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

+3
View File
@@ -0,0 +1,3 @@
*.js
*.log
*.ppm
+53
View File
@@ -0,0 +1,53 @@
# 1 Billion Row Challenge (1BRC)
A solution to the [1 Billion Row Challenge](https://www.morling.dev/blog/one-billion-row-challenge/),
written in [the V programming language](https://vlang.io/).
Read more about the challenge here: https://www.morling.dev/blog/one-billion-row-challenge/
## Running instructions
Suggested compiler options for performance:
`v -cc gcc -prod -cflags "-std=c17 -march=native -mtune=native" .`
### Step 1: Create a measurements file
Compile and run `make-samples` to create the sample file.
```
cd make-samples
v -cc gcc -prod -cflags "-std=c17 -march=native -mtune=native" .
./make-samples 1000000000 > ~/measurements.txt
```
NOTE: If you create a billion rows, the file will be about 12GB!
### Step 2: Run (and time) the solution
```
cd solution
v -cc gcc -prod -cflags "-std=c17 -march=native -mtune=native" .
./solution ~/measurements.txt
```
You can time the solution using `v time`:
`v time ./solution ~/measurements.txt`
By default, the solution runs in a single thread. If you want to run
parallel processing, use the `-n` parameter, for example, to run with
8 threads:
`./solution -n 8 ~/measurements.txt`
On Linux, to run one thread per core, use
`./solution -n $(nproc) ~/measurements.txt`
### Step 3: Improve upon the solution
Make changes that improve the performance and submit them.
Let's show off what is possible in V!
@@ -0,0 +1,413 @@
Abha,18.0
Abidjan,26.0
Abéché,29.4
Accra,26.4
Addis Ababa,16.0
Adelaide,17.3
Aden,29.1
Ahvaz,25.4
Albuquerque,14.0
Alexandra,11.0
Alexandria,20.0
Algiers,18.2
Alice Springs,21.0
Almaty,10.0
Amsterdam,10.2
Anadyr,-6.9
Anchorage,2.8
Andorra la Vella,9.8
Ankara,12.0
Antananarivo,17.9
Antsiranana,25.2
Arkhangelsk,1.3
Ashgabat,17.1
Asmara,15.6
Assab,30.5
Astana,3.5
Athens,19.2
Atlanta,17.0
Auckland,15.2
Austin,20.7
Baghdad,22.77
Baguio,19.5
Baku,15.1
Baltimore,13.1
Bamako,27.8
Bangkok,28.6
Bangui,26.0
Banjul,26.0
Barcelona,18.2
Bata,25.1
Batumi,14.0
Beijing,12.9
Beirut,20.9
Belgrade,12.5
Belize City,26.7
Benghazi,19.9
Bergen,7.7
Berlin,10.3
Bilbao,14.7
Birao,26.5
Bishkek,11.3
Bissau,27.0
Blantyre,22.2
Bloemfontein,15.6
Boise,11.4
Bordeaux,14.2
Bosaso,30.0
Boston,10.9
Bouaké,26.0
Bratislava,10.5
Brazzaville,25.0
Bridgetown,27.0
Brisbane,21.4
Brussels,10.5
Bucharest,10.8
Budapest,11.3
Bujumbura,23.8
Bulawayo,18.9
Burnie,13.1
Busan,15.0
Cabo San Lucas,23.9
Cairns,25.0
Cairo,21.4
Calgary,4.4
Canberra,13.1
Cape Town,16.2
Changsha,17.4
Charlotte,16.1
Chiang Mai,25.8
Chicago,9.8
Chihuahua,18.6
Chișinău,10.2
Chittagong,25.9
Chongqing,18.6
Christchurch,12.2
City of San Marino,11.8
Colombo,27.4
Columbus,11.7
Conakry,26.4
Copenhagen,9.1
Cotonou,27.2
Cracow,9.3
Da Lat,17.9
Da Nang,25.8
Dakar,24.0
Dallas,19.0
Damascus,17.0
Dampier,26.4
Dar es Salaam,25.8
Darwin,27.6
Denpasar,23.7
Denver,10.4
Detroit,10.0
Dhaka,25.9
Dikson,-11.1
Dili,26.6
Djibouti,29.9
Dodoma,22.7
Dolisie,24.0
Douala,26.7
Dubai,26.9
Dublin,9.8
Dunedin,11.1
Durban,20.6
Dushanbe,14.7
Edinburgh,9.3
Edmonton,4.2
El Paso,18.1
Entebbe,21.0
Erbil,19.5
Erzurum,5.1
Fairbanks,-2.3
Fianarantsoa,17.9
Flores, Petén,26.4
Frankfurt,10.6
Fresno,17.9
Fukuoka,17.0
Gabès,19.5
Gaborone,21.0
Gagnoa,26.0
Gangtok,15.2
Garissa,29.3
Garoua,28.3
George Town,27.9
Ghanzi,21.4
Gjoa Haven,-14.4
Guadalajara,20.9
Guangzhou,22.4
Guatemala City,20.4
Halifax,7.5
Hamburg,9.7
Hamilton,13.8
Hanga Roa,20.5
Hanoi,23.6
Harare,18.4
Harbin,5.0
Hargeisa,21.7
Hat Yai,27.0
Havana,25.2
Helsinki,5.9
Heraklion,18.9
Hiroshima,16.3
Ho Chi Minh City,27.4
Hobart,12.7
Hong Kong,23.3
Honiara,26.5
Honolulu,25.4
Houston,20.8
Ifrane,11.4
Indianapolis,11.8
Iqaluit,-9.3
Irkutsk,1.0
Istanbul,13.9
İzmir,17.9
Jacksonville,20.3
Jakarta,26.7
Jayapura,27.0
Jerusalem,18.3
Johannesburg,15.5
Jos,22.8
Juba,27.8
Kabul,12.1
Kampala,20.0
Kandi,27.7
Kankan,26.5
Kano,26.4
Kansas City,12.5
Karachi,26.0
Karonga,24.4
Kathmandu,18.3
Khartoum,29.9
Kingston,27.4
Kinshasa,25.3
Kolkata,26.7
Kuala Lumpur,27.3
Kumasi,26.0
Kunming,15.7
Kuopio,3.4
Kuwait City,25.7
Kyiv,8.4
Kyoto,15.8
La Ceiba,26.2
La Paz,23.7
Lagos,26.8
Lahore,24.3
Lake Havasu City,23.7
Lake Tekapo,8.7
Las Palmas de Gran Canaria,21.2
Las Vegas,20.3
Launceston,13.1
Lhasa,7.6
Libreville,25.9
Lisbon,17.5
Livingstone,21.8
Ljubljana,10.9
Lodwar,29.3
Lomé,26.9
London,11.3
Los Angeles,18.6
Louisville,13.9
Luanda,25.8
Lubumbashi,20.8
Lusaka,19.9
Luxembourg City,9.3
Lviv,7.8
Lyon,12.5
Madrid,15.0
Mahajanga,26.3
Makassar,26.7
Makurdi,26.0
Malabo,26.3
Malé,28.0
Managua,27.3
Manama,26.5
Mandalay,28.0
Mango,28.1
Manila,28.4
Maputo,22.8
Marrakesh,19.6
Marseille,15.8
Maun,22.4
Medan,26.5
Mek'ele,22.7
Melbourne,15.1
Memphis,17.2
Mexicali,23.1
Mexico City,17.5
Miami,24.9
Milan,13.0
Milwaukee,8.9
Minneapolis,7.8
Minsk,6.7
Mogadishu,27.1
Mombasa,26.3
Monaco,16.4
Moncton,6.1
Monterrey,22.3
Montreal,6.8
Moscow,5.8
Mumbai,27.1
Murmansk,0.6
Muscat,28.0
Mzuzu,17.7
N'Djamena,28.3
Naha,23.1
Nairobi,17.8
Nakhon Ratchasima,27.3
Napier,14.6
Napoli,15.9
Nashville,15.4
Nassau,24.6
Ndola,20.3
New Delhi,25.0
New Orleans,20.7
New York City,12.9
Ngaoundéré,22.0
Niamey,29.3
Nicosia,19.7
Niigata,13.9
Nouadhibou,21.3
Nouakchott,25.7
Novosibirsk,1.7
Nuuk,-1.4
Odesa,10.7
Odienné,26.0
Oklahoma City,15.9
Omaha,10.6
Oranjestad,28.1
Oslo,5.7
Ottawa,6.6
Ouagadougou,28.3
Ouahigouya,28.6
Ouarzazate,18.9
Oulu,2.7
Palembang,27.3
Palermo,18.5
Palm Springs,24.5
Palmerston North,13.2
Panama City,28.0
Parakou,26.8
Paris,12.3
Perth,18.7
Petropavlovsk-Kamchatsky,1.9
Philadelphia,13.2
Phnom Penh,28.3
Phoenix,23.9
Pittsburgh,10.8
Podgorica,15.3
Pointe-Noire,26.1
Pontianak,27.7
Port Moresby,26.9
Port Sudan,28.4
Port Vila,24.3
Port-Gentil,26.0
Portland {OR},12.4
Porto,15.7
Prague,8.4
Praia,24.4
Pretoria,18.2
Pyongyang,10.8
Rabat,17.2
Rangpur,24.4
Reggane,28.3
Reykjavík,4.3
Riga,6.2
Riyadh,26.0
Rome,15.2
Roseau,26.2
Rostov-on-Don,9.9
Sacramento,16.3
Saint Petersburg,5.8
Saint-Pierre,5.7
Salt Lake City,11.6
San Antonio,20.8
San Diego,17.8
San Francisco,14.6
San Jose,16.4
San José,22.6
San Juan,27.2
San Salvador,23.1
Sana'a,20.0
Santo Domingo,25.9
Sapporo,8.9
Sarajevo,10.1
Saskatoon,3.3
Seattle,11.3
Ségou,28.0
Seoul,12.5
Seville,19.2
Shanghai,16.7
Singapore,27.0
Skopje,12.4
Sochi,14.2
Sofia,10.6
Sokoto,28.0
Split,16.1
St. John's,5.0
St. Louis,13.9
Stockholm,6.6
Surabaya,27.1
Suva,25.6
Suwałki,7.2
Sydney,17.7
Tabora,23.0
Tabriz,12.6
Taipei,23.0
Tallinn,6.4
Tamale,27.9
Tamanrasset,21.7
Tampa,22.9
Tashkent,14.8
Tauranga,14.8
Tbilisi,12.9
Tegucigalpa,21.7
Tehran,17.0
Tel Aviv,20.0
Thessaloniki,16.0
Thiès,24.0
Tijuana,17.8
Timbuktu,28.0
Tirana,15.2
Toamasina,23.4
Tokyo,15.4
Toliara,24.1
Toluca,12.4
Toronto,9.4
Tripoli,20.0
Tromsø,2.9
Tucson,20.9
Tunis,18.4
Ulaanbaatar,-0.4
Upington,20.4
Ürümqi,7.4
Vaduz,10.1
Valencia,18.3
Valletta,18.8
Vancouver,10.4
Veracruz,25.4
Vienna,10.4
Vientiane,25.9
Villahermosa,27.1
Vilnius,6.0
Virginia Beach,15.8
Vladivostok,4.9
Warsaw,8.5
Washington, D.C.,14.6
Wau,27.8
Wellington,12.9
Whitehorse,-0.1
Wichita,13.9
Willemstad,28.0
Winnipeg,3.0
Wrocław,9.6
Xi'an,14.1
Yakutsk,-8.8
Yangon,27.5
Yaoundé,23.8
Yellowknife,-4.3
Yerevan,12.4
Yinchuan,9.0
Zagreb,10.7
Zanzibar City,26.0
Zürich,9.3
+40
View File
@@ -0,0 +1,40 @@
import encoding.csv
import flag
import os
import rand
struct CityMean {
city string
mean f64
}
fn main() {
mut fp := flag.new_flag_parser(os.args)
fp.version('1brc sample generator v1.0.0')
fp.skip_executable()
fp.application('Sample generator for 1 billion rows challenge')
fp.description('The 1 billion rows challenge solved in V.\nFor details, see https://www.morling.dev/blog/one-billion-row-challenge/')
input_file := fp.string('city-file', `i`, 'cities.txt',
'Path to input file with cities and means list')
fp.limit_free_args_to_exactly(1)!
sample_count := fp.remaining_parameters()[0].u64()
content := os.read_file(input_file) or { panic(err) }
mut reader := csv.new_reader(content, csv.ReaderConfig{ delimiter: `,` })
mut means := []CityMean{}
for {
rec := reader.read() or { break }
means << CityMean{
city: rec[0]
mean: rec[1].f64()
}
}
for _ in 0 .. sample_count / 2 {
mut city := rand.intn(means.len)!
m1, m2 := rand.normal_pair(mu: means[city].mean, sigma: 10)!
println('${means[city].city};${m1:.1f}')
city = rand.intn(means.len)!
println('${means[city].city};${m2:.1f}')
}
}
+202
View File
@@ -0,0 +1,202 @@
// vtest build: !windows // requires mmap
import flag
import math
import os
#include <sys/mman.h>
fn C.mmap(addr voidptr, len u64, prot i32, flags i32, fd i32, offset i64) voidptr
fn C.munmap(addr voidptr, len u64) i32
struct MemoryMappedFile {
size u64
mut:
data &u8
file os.File
}
fn mmap_file(path string) MemoryMappedFile {
mut mf := MemoryMappedFile{
file: os.open_file(path, 'r', 0) or { panic('fail') }
size: os.file_size(path)
data: C.NULL
}
mf.data = &u8(C.mmap(C.NULL, mf.size, C.PROT_READ, C.MAP_SHARED, mf.file.fd, 0))
return mf
}
fn (mut mf MemoryMappedFile) unmap() {
if C.munmap(mf.data, mf.size) != 0 {
panic('(${C.errno}) munmap() failed')
}
mf.file.close()
}
enum ReadState {
city
temp
}
struct Result {
pub mut:
min i32
max i32
sum i32
count u32
}
fn format_value(value i32) string {
return '${value / 10}.${math.abs(value % 10)}'
}
fn print_results(results map[string]Result, print_nicely bool) {
mut output := []string{cap: results.len}
mut cities := results.keys()
cities.sort()
for city in cities {
v := results[city]
mean := f64(v.sum) / v.count / 10
output << '${city}=${format_value(v.min)}/${mean:.1f}/${format_value(v.max)}'
}
if print_nicely {
println(output.join('\n'))
} else {
println('{' + output.join(', ') + '}')
}
}
fn combine_results(results []map[string]Result) map[string]Result {
mut combined_result := map[string]Result{}
for result in results {
for city, r in result {
if city !in combined_result {
combined_result[city] = r
} else {
if r.max > combined_result[city].max {
combined_result[city].max = r.max
}
if r.min < combined_result[city].min {
combined_result[city].min = r.min
}
combined_result[city].sum += r.sum
combined_result[city].count += r.count
}
}
}
return combined_result
}
@[direct_array_access]
fn process_chunk(addr &u8, from u64, to u64) map[string]Result {
mut results := map[string]Result{}
mut state := ReadState.city
mut city := ''
mut temp := i32(0)
mut mod := i32(1)
mut j := int(0)
for i in from .. to {
c := unsafe { u8(addr[i]) }
match state {
.city {
match c {
`;` {
state = .temp
city = unsafe { tos(addr[i - u64(j)], j) }
}
else {
j += 1
}
}
}
.temp {
match c {
`\n` {
temp *= mod
if city !in results {
results[city] = Result{
min: temp
max: temp
sum: temp
count: 1
}
} else {
if temp > results[city].max {
results[city].max = temp
}
if temp < results[city].min {
results[city].min = temp
}
results[city].sum += temp
results[city].count += 1
}
state = .city
temp = 0
mod = 1
j = 0
}
`-` {
mod = -1
}
`.` {}
else {
// ASCII 48 = '0' ... ASCII 57 = '9' => (ASCII value) - 48 = decimal value
temp = temp * 10 + (c - 48)
}
}
}
}
}
return results
}
fn process_in_parallel(mf MemoryMappedFile, thread_count u32) map[string]Result {
mut threads := []thread map[string]Result{}
approx_chunk_size := mf.size / thread_count
mut from := u64(0)
mut to := approx_chunk_size
for _ in 0 .. thread_count - 1 {
unsafe {
for mf.data[to] != `\n` {
to += 1
}
}
threads << spawn process_chunk(mf.data, from, to)
from = to + 1
to = from + approx_chunk_size
}
to = mf.size
threads << spawn process_chunk(mf.data, from, to)
res := threads.wait()
return combine_results(res)
}
fn main() {
mut fp := flag.new_flag_parser(os.args)
fp.version('1brc v1.0.0')
fp.skip_executable()
fp.application('1 billion rows challenge')
fp.description('The 1 billion rows challenge solved in V.\nFor details, see https://www.morling.dev/blog/one-billion-row-challenge/')
thread_count := u32(fp.int('threads', `n`, 1, 'number of threads for parallel processing.'))
print_nicely := fp.bool('human-readable', `h`, false,
'Print results with new lines rather than following challenge spec')
quiet := fp.bool('quiet', `q`, false,
'Suppress the results output (e.g., if you only care about timing)')
fp.limit_free_args_to_exactly(1)!
path := fp.remaining_parameters()[0]
mut mf := mmap_file(path)
defer {
mf.unmap()
}
results := if thread_count > 1 {
process_in_parallel(mf, thread_count)
} else {
process_chunk(mf.data, 0, mf.size)
}
if !quiet {
print_results(results, print_nicely)
}
}
+6
View File
@@ -0,0 +1,6 @@
2048.wasm
2048
main
index.js
index.wasm
File diff suppressed because it is too large. Load diff
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Delyan Angelov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+62
View File
@@ -0,0 +1,62 @@
# V 2048
This is a simple 2048 game, written in [the V programming language](https://vlang.io/).
WebAssembly demo: https://v2048.vercel.app
![screenshot](demo.png)
## Description
Merge tiles by moving them.
After each move, a new random tile is added (2 or 4).
The goal of the game is to create a tile with a value of 2048.
## Keys
Escape - exit the game
Backspace - undo last move
n - restart the game
t - toggle the UI theme
Enter - toggle the tile text format
UP,LEFT,DOWN,RIGHT / W,A,S,D / touchscreen swipes - move the tiles
## Running instructions
Compile & run the game with `./v run examples/2048`
## Compiling to WASM
1. Install Emscripten from:
https://emscripten.org/docs/getting_started/downloads.html
2. Make sure that the environment in your shell is setup correctly,
i.e. that `emcc --version` works.
```sh
. /opt/emsdk/emsdk_env.sh
emcc --version
```
3. Compile the game to WASM:
(the JS file contains a loader for the .wasm file, without the extension):
```sh
v -prod -os wasm32_emscripten -o examples/2048/index.js examples/2048/
```
5. Run/test the game:
```sh
emrun examples/2048/index.html
```
If you prefer, instead of `emrun`, you can start a simple V http file server:
```sh
v -e 'import net.http.file; file.serve(folder: "examples/2048")'
```
and then open http://127.0.0.1:4001/ in your browser.
Once you have opened the game in your browser, you can make changes to
the V source, then recompile (step 3), and refresh the browser window
with the game, to see their effect.
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+36
View File
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset=utf-8>
<meta content="text/html; charset=utf-8" http-equiv=Content-Type>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v 2048</title>
<style>
body {
margin-left: 1rem;
background-color: rgb(250, 248, 239);
}
</style>
</head>
<body>
<h1>V 2048 demo</h1>
<h3>
Authors: <a href="http://github.com/spaceface777">@spaceface777</a>, <a href="http://github.com/spytheman">@spytheman</a> and other V contributors.
<br>
Original game by <a href="http://github.com/gabrielecirulli">@gabrielecirulli</a>.
</h3>
<h3>Click on the canvas to go into fullscreen mode</h3>
<canvas id=canvas oncontextmenu=event.preventDefault() tabindex=-1 width="544" height="560"></canvas>
<script src=index.js></script>
<script>
const m = Module()
const e = document.getElementById("canvas");
e.onclick = e.ontouchstart = () => {
e.requestFullscreen()
}
</script>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
Module {
name: 'v2048',
description: 'A simple 2048 game written in V.',
version: '0.0.2',
repo_url: 'https://github.com/spytheman/v2048',
dependencies: []
}
+92
View File
@@ -0,0 +1,92 @@
import term.ui as tui
import flag
struct DocTest {
show_version bool @[short: v; xdoc: 'Show version and exit']
debug_level int @[long: debug; short: d; xdoc: 'Debug level']
level f32 @[only: l; xdoc: 'Do not show this']
example string
square bool
multi int @[only: m; repeats]
wroom []int @[short: w]
the_limit string
}
struct App {
mut:
tui &tui.Context = unsafe { nil }
frame int
square string = '.____.\n| |\n| |\n|____|'
pad int = 1
direction int = 1
}
fn event(e &tui.Event, mut _app App) {
match e.typ {
.mouse_down {}
.mouse_drag {}
.mouse_up {}
.key_down {
if e.code == .c {
} else if e.code == .escape {
exit(0)
}
}
else {}
}
}
fn frame(mut app App) {
app.tui.clear()
app.frame++
if app.frame % 4 == 0 {
app.pad += app.direction
app.square = '${' '.repeat(app.pad)}.____.\n${' '.repeat(app.pad)}| |\n${' '.repeat(app.pad)}| |\n${' '.repeat(app.pad)}|____|'
}
if app.frame % 100 == 0 {
if app.direction > 0 {
app.direction = -1
} else {
app.direction = 1
}
}
help_text := flag.to_doc[DocTest](
version: '1.0'
description: 'Hello! This should show an *animated* example application description.
We are at frame ${app.frame}.
Press ESCAPE or Ctrl+C to exit'
footer: '
Press ESCAPE or Ctrl+C to exit'
fields: {
'level': 'Level of lorem ipsum\nand more\nmany many many more.\nNotice how user newlines/format is kept since\ninput lines are all less or within\nthe default layout.description_padding\nand max width'
'example': 'Looong example text without newlines or anything else and lorem ipsum and more and many many many more. Should be auto fitted'
'multi': 'This flag can be repeated'
'-e, --extra': 'Secret flag that does not exist on the struct, but we want documented (in same format as the others)'
'-q, --quiet-and-quite-long-flag <string>': 'Mega long description and secret flag that does not exist on the struct, but we want documented. Also the flag has custom newlines\nand the flag line itself is super long'
'square': '${app.square}'
}
) or { '' }
app.tui.draw_text(0, 0, '${help_text}')
app.tui.reset()
app.tui.flush()
}
type EventFn = fn (&tui.Event, voidptr)
type FrameFn = fn (voidptr)
fn main() {
mut app := &App{}
app.tui = tui.init(
user_data: app
event_fn: EventFn(event)
frame_fn: FrameFn(frame)
hide_cursor: true
frame_rate: 60
)
app.tui.run()!
}
+165
View File
@@ -0,0 +1,165 @@
import archive.tar
import flag
import net.http
import os
import term
const default_url = 'https://github.com/vlang/v/archive/refs/tags/v0.1.3.tar.gz'
@[heap]
struct Context {
url string // Web starting with http:// or https://. Local starting with file:///
chunks bool // true: decompress with callback
debug int // print debug lines
max_blocks int // if max_blocks > 0 and is reached stops early.
filename string // if filename is found as a path of a data block, stops early.
}
fn (ctx &Context) read_last_block(mut read tar.Read) bool {
if ctx.max_blocks > 0 && ctx.max_blocks < read.get_block_number() {
read.stop_early = true
return true
}
return false
}
fn new_context() !&Context {
mut fp := flag.new_flag_parser(os.args)
fp.application('tar_gz_reader')
fp.version('0.0.20250721')
fp.description('Reads into memory selected sections of *.tar.gz. archives from https or home_dir.')
fp.skip_executable()
ctx := &Context{
url: fp.string('url', `u`, default_url,
'archive *.tar.gz URL, default(${default_url}). Start name with file:/// for local')
chunks: fp.bool('chunks', `c`, false,
'decompress with chunks to reduce RAM usage, default(false)')
debug: fp.int('debug', `d`, 0,
'prints blocks: 1=other, 2:+dirs, 3=+files, 4=+data, default(0=silent)')
max_blocks: fp.int('max_blocks', `m`, 0,
'maximum blocks to read, stop early. Default(0=read all)')
filename: fp.string('filename', `f`, '',
'filename content complete print, stop early. Default(empty means none)')
}
additional := fp.finalize()!
if additional.len > 0 {
println('unprocessed args ${additional.join_lines()}')
}
return ctx
}
// Downloader downloads a *.tar.gz using HTTP chunks
struct Downloader {
mut:
chunks int
data []u8
}
fn new_downloader(url string) !&Downloader {
mut downloader := &Downloader{}
params := http.DownloaderParams{
downloader: downloader
}
if url.starts_with('http://') || url.starts_with('https://') {
http.download_file_with_progress(url, '', params)!
} else if url.starts_with('file:///') {
path := '${os.home_dir()}/${url[8..]}'
println('path ${path}')
downloader.data = os.read_bytes(path)!
}
return downloader
}
fn (mut d Downloader) on_start(mut _request http.Request, _path string) ! {}
fn (mut d Downloader) on_chunk(_request &http.Request, chunk []u8, _already_received u64, expected u64) ! {
if expected == 0 {
return
}
d.chunks++
d.data << chunk
}
fn (mut d Downloader) on_finish(_request &http.Request, _response &http.Response) ! {}
struct FileReader implements tar.Reader {
ctx &Context
mut:
filepath string
content []u8
}
fn (mut f FileReader) other_block(mut read tar.Read, details string) {
if f.ctx.read_last_block(mut read) {
return
}
if f.ctx.debug > 0 {
row := 'OTHER block:${read.get_block_number():6} ${read.get_special()} ${details} ${read.get_path()} '
println(term.colorize(term.bright_yellow, row))
}
}
fn (mut f FileReader) dir_block(mut read tar.Read, size u64) {
if f.ctx.read_last_block(mut read) {
return
}
if f.ctx.debug > 1 {
row := 'DIR block:${read.get_block_number():6} ${read.get_path()} size:${size}'
println(term.colorize(term.green, row))
}
}
fn (mut f FileReader) file_block(mut read tar.Read, size u64) {
if f.ctx.read_last_block(mut read) {
return
}
path := read.get_path()
if f.ctx.debug > 2 {
row := ' FILE block:${read.get_block_number():6} ${path} size:${size}'
println(term.colorize(term.bright_blue, row))
}
if f.ctx.filename != '' && f.filepath == '' && path.ends_with(f.ctx.filename) {
f.filepath = path
}
}
fn (mut f FileReader) data_block(mut read tar.Read, data []u8, pending int) {
if f.ctx.read_last_block(mut read) {
return
}
path := read.get_path()
if f.ctx.debug > 3 {
println(' DATA block:${read.get_block_number():6} ${path} len:${data.len} pend:${pending}')
}
if f.ctx.filename != '' {
if f.filepath == path {
f.content << data
if pending == 0 {
// our file of interest data is complete
read.stop_early = true
}
}
}
}
fn main() {
ctx := new_context()!
reader := FileReader{
ctx: ctx
}
mut untar := tar.new_untar(reader)
mut decompressor := tar.new_decompressor(untar)
downloader := new_downloader(ctx.url)!
if ctx.chunks {
decompressor.read_chunks(downloader.data)!
} else {
decompressor.read_all(downloader.data)!
}
println('-'.repeat(80))
println('Download: ${ctx.url} chunks:${downloader.chunks} bytes=${downloader.data.len}')
println('Untar: ${untar}')
println('Content: Path:${reader.filepath} bytes:${reader.content.len}')
println('-'.repeat(80))
println('${reader.content.bytestr()}')
println('-'.repeat(80))
}
+19
View File
@@ -0,0 +1,19 @@
// vtest build: !msvc
fn main() {
a := 100
b := 20
mut c := 0
$if amd64 {
asm amd64 {
mov eax, a
add eax, b
mov c, eax
; =r (c) // output
; r (a) // input
r (b)
}
}
println('a: ${a}') // 100
println('b: ${b}') // 20
println('c: ${c}') // 120
}
Binary file not shown.
Binary file not shown.
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+93
View File
@@ -0,0 +1,93 @@
Copyright (c) 2012, Eduardo Tunni (http://www.tipo.net.ar), with Reserved Font Name "Imprima"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+1
View File
@@ -0,0 +1 @@
P0;0;0q"1;1;128;128#0;2;0;0;0#1;2;26;35;45#2;2;27;35;45#3;2;27;35;45#4;2;27;35;45#5;2;27;35;45#6;2;27;35;45#7;2;27;35;45#8;2;27;35;46#9;2;27;36;46#10;2;27;35;45#11;2;27;36;45#12;2;27;36;46#13;2;28;36;46#14;2;27;36;47#15;2;28;36;46#16;2;28;36;47#17;2;28;36;47#18;2;27;36;47#19;2;27;36;48#20;2;27;37;48#21;2;28;37;49#22;2;28;36;47#23;2;29;37;47#24;2;29;37;48#25;2;29;37;48#26;2;29;38;48#27;2;29;38;49#28;2;29;38;49#29;2;29;38;49#30;2;29;38;50#31;2;30;38;49#32;2;30;38;49#33;2;30;38;50#34;2;30;39;50#35;2;29;38;51#36;2;29;39;51#37;2;30;39;50#38;2;30;39;51#39;2;30;39;51#40;2;31;39;51#41;2;31;40;51#42;2;31;40;51#43;2;31;40;52#44;2;30;40;53#45;2;29;40;53#46;2;31;41;55#47;2;31;40;51#48;2;31;40;52#49;2;31;41;53#50;2;32;41;53#51;2;32;41;53#52;2;32;41;53#53;2;32;41;53#54;2;32;42;53#55;2;32;42;54#56;2;33;42;54#57;2;32;42;54#58;2;33;42;54#59;2;32;42;55#60;2;33;42;54#61;2;32;42;54#62;2;33;42;54#63;2;32;42;55#64;2;33;42;55#65;2;33;42;54#66;2;33;42;54#67;2;33;42;54#68;2;33;42;55#69;2;32;42;54#70;2;33;42;54#71;2;32;42;54#72;2;33;42;54#73;2;33;42;55#74;2;33;42;54#75;2;33;43;56#76;2;32;43;57#77;2;33;43;56#78;2;33;44;58#79;2;33;44;59#80;2;35;45;58#81;2;33;45;61#82;2;34;45;61#83;2;36;47;61#84;2;36;47;63#85;2;36;48;63#86;2;36;49;65#87;2;38;49;65#88;2;37;51;67#89;2;36;51;72#90;2;36;52;73#91;2;36;53;74#92;2;36;53;74#93;2;36;53;75#94;2;36;53;75#95;2;36;53;75#96;2;36;53;75#97;2;36;53;75#98;2;37;53;75#99;2;36;53;75#100;2;36;53;75#101;2;36;53;75#102;2;37;53;75#103;2;37;53;75#104;2;37;54;75#105;2;39;51;67#106;2;39;51;68#107;2;40;52;70#108;2;40;53;71#109;2;42;54;69#110;2;41;53;70#111;2;42;54;71#112;2;39;53;73#113;2;39;53;72#114;2;40;55;73#115;2;38;54;74#116;2;38;54;74#117;2;38;54;75#118;2;38;54;75#119;2;39;54;75#120;2;40;54;74#121;2;39;54;74#122;2;41;54;72#123;2;36;53;75#124;2;36;53;75#125;2;36;53;75#126;2;37;53;75#127;2;37;53;75#128;2;36;53;75#129;2;36;53;75#130;2;38;54;75#0!128~-!9~^!8N!11^!70~!11^!8N^!9~$#97!9?_ooo_oo_o!8_#129??_#67!70?_#72___#58!7_o_!6o_$#125!13?O??O!9?__#67!83?O-#0!9~}o#101_#125!22?@#101??@!5?C#58!38?_w{!6}!26~N@$#125!9?@K#0!28?!4@BF^!36~^FB!4@#67@@#0!26?o}!9~$#97!10?B^!22~}~~}~!4}ww_-#0!11~}o!33?B^!32~^B!33?o}!11~$#125!11?@?_#129!33?_#58!32?_{!33~N@$#97!12?F^!32~{$#129!12?G-#0!14~w!33?@N!28~NB!33?w!14~$#97!14?Bn!32~}_#58!28?o{!33~F$#125!14?CO#129!33?O-#0!16~w#125O#0!32?@N!24~N@#74!32?_#0w!16~$#97!16?Fn!32~{O#58!24?ow!32~^B$#101!50?A#125_#67!25?C#72!33?C$#73!77?A-#0!18~{_!32?@N!20~N@!32?_{!18~$#97!18?B^!32~}_#58!20?o}!32~^B$#125!53?O-#0!20~{_!32?@F!16~N@!32?_{!20~$#97!20?BN!32~{o#58!16?o}!32~VB$#101!21?O#129!32?A#125G#67!50?G-#0!22~}o#129!32?@#0F!12~F@!32?o}!22~$#97!22?@N!32~}o#58!12?w}!32~N@$#101!57?G-#0!24~}o#125_!31?A#0F!8~F!33?o}!24~$#101!24?@#97N^!31~|w#58!8?o!33~N@$#72!68?G-#0!26~}w#125!32?@?_#58???o}!32~F#72@$#97!26?@F!32~}w#64!4?G#72@#0!32?w}!26~$!61?F^~~~F-!29~w!33?BF#58FN^!29~nB$#97!29?F!33~w#41G?_#72!30?OC$#90!63?C#77O#52GO_#0!30?w!29~$#109!64?_#23_$#37!65?O-#0!31~{_#119!31?@#58A??BN!25~^B$#97!31?B^!31~}o#31@?_#0!26?_{!31~$#7!65?@C#41AO$#109!65?C#23AO#52Co$#130!65?G#1G#48@G$#75!66?O#4_$#111!66?_#28G$#37!67?C-#0!33~{_#101!31?@#7@G#31C_#0!22?_{!33~$#97!33?BN!31~}w#4C_#58@N!21~^B$#125!34?O#80!32?A#17AO#52Eo$#122!67?C#28@#41AO$#83!68?O#48@G$#114!68?_#26G-#0!35~{o#120!32?C_#31C_#0!18?o{!35~$#101!35?@#97F!32~w#4C_#58BN!17~FB$#125!35?A#129G#19!32?@#23@G#52Co#72!17?G$#85!69?A#16A#41AO$#21!70?G#44@G$#87!70?O#7O-#0!37~}o#119!32?C_#31C_#0!14?o}!37~$#125!37?@G#36!32?@#44G@G$#97!38?F!32~w#7Ao#58@N!13~N@$#105!71?A#23@G#52Eo$#4!72?C#41A#37O$#106!72?O-#0!39~}w#46!32?@#7EO#58@N!9~F#67@$#125!39?@#97F!32~w_#28C_#0!10?w}!39~$#101!73?C#23@G#48C_$#107!73?A#76G#37AO$#108!74?O#19_#52AO$#44!75?@#41G-#0!42~w#101o#79!31?@#23@G#48C_#0!5?w!42~$#97!42?FN!31~{_#41@G#58F!5~F$#114!75?A#4A#7O#52BW$#19!76?C#21_#26_$#82!76?G#28C#31O$#112!76?O#37A-#0!44~w_#119!31?AO#31AO#58F^B$#97!44?F^!31~{_#41@G#0?_w!44~$#84!77?@#4A#7O#48C_#72?C$#17!78?@#16G#52BW$#36!78?C#26C#28_$#86!78?G#45_-#0!46~{w!33o{!46~$#97!46?@FF!30NK#23@#44@$#125!46?A#89?G#88!30?@#28A#47A$#101!79?A#78C$#81!80?G-#0!128~-!128B-\
+4
View File
@@ -0,0 +1,4 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#536b8a" d="M 323.509,471.167 480.202,32.185 C 482.857,24.74 478.671,19.312 470.853,20.071 L 347.629,32.03 c -7.816,0.759 -16.259,7.44 -18.841,14.911 L 183.753,466.465 c -2.583,7.47 1.693,13.535 9.546,13.535 h 119.938 c 3.925,0 8.192,-3.02 9.52,-6.744 l 0.746,-2.089 v 0 0 z"/>
<path fill="#5d87be" d="M 30.055,20.07 152.165,32.029 c 7.745,0.76 16.122,7.438 18.692,14.905 l 146.766,426.302 c 1.284,3.735 -0.83,6.764 -4.721,6.764 H 193.294 c -7.781,0 -16.239,-6.044 -18.871,-13.489 L 20.791,32.185 c -2.633,-7.445 1.518,-12.874 9.264,-12.114 v 0 0 z"/>
</svg>

After

Width:  |  Height:  |  Size: 671 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#536b8a" d="m10.01,14.308l4.189,-12.212c0.071,-0.207 -0.041,-0.358 -0.25,-0.337l-3.295,0.333c-0.209,0.021 -0.435,0.207 -0.504,0.415l-3.878,11.670c-0.069,0.208 0.045,0.376 0.255,0.376l3.207,0c0.105,0 0.219,-0.084 0.255,-0.188l0.02,-0.058l0,0l0,0z"/>
<path fill="#5d87be" d="m2.267,1.759l3.247,0.333c0.206,0.021 0.428,0.207 0.497,0.415l3.903,11.859c0.034,0.104 -0.022,0.188 -0.126,0.188l-3.18,0c-0.207,0 -0.431,-0.168 -0.502,-0.375l-4.086,-12.082c-0.07,-0.207 0.04,-0.358 0.246,-0.337l0,0l0,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 610 B

+3
View File
@@ -0,0 +1,3 @@
asteroids
asteroids.js
asteroids.wasm
+20
View File
@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>V Asteroids</title>
</head>
<body>
<h1>V Asteroids demo</h1>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1" width="800" height="600"></canvas>
<script type="text/javascript">
var canvasElement = document.getElementById('canvas');
var Module = {
print(...args) { console.log(...args); },
};
</script>
<script src="asteroids.js"></script>
</body>
</html>
+386
View File
@@ -0,0 +1,386 @@
// Copyright (c) 2025 Delyan Angelov. All rights reserved.
// The use of this source code is governed by an MIT license that
// can be found in the LICENSE file.
module main
import gg
import math
import rand
import os.asset
import math.vec
type V2 = vec.Vec2[f32]
fn rads(degrees f32) f32 {
return f32(math.radians(degrees))
}
fn (a V2) offset(angle f32, scale f32) V2 {
return a + V2{math.cosf(angle) * scale, math.sinf(angle) * scale}
}
fn (mut a V2) wrap(b V2) {
a.x = f32(math.mod(a.x + b.x, b.x))
a.y = f32(math.mod(a.y + b.y, b.y))
}
fn V2.random(b V2) V2 {
return V2{rand.f32() * b.x, rand.f32() * b.y}
}
struct Body {
mut:
pos V2
vel V2
rotation f32
radius f32
active bool = true
}
struct Bullet {
Body
}
struct Asteroid {
Body
mut:
segments int
offsets []f32
points []f32
rotation_step f32 = 1.0
}
struct Player {
Body
mut:
is_engine_on bool
bullets int = 99
bullets_limit int = 99
fuel int = 9999
fuel_limit int = 9999
cooldown int
cooldown_frames int = 15
points []f32 = []f32{len: 8}
}
struct Game {
mut:
gg &gg.Context = unsafe { nil }
screen V2 = V2{800, 600}
player Player
bullets []Bullet
asteroids []Asteroid
score int
highscore int
ships int = 5
level int = 1
is_up bool
msg Message
}
@[params]
struct Message {
mut:
frames int
text string
size int = 40
color gg.Color
align gg.HorizontalAlign = .center
}
fn (mut a Asteroid) setup() {
a.segments = int(a.radius / 10) * 10
a.offsets = []f32{len: a.segments}
a.points = []f32{len: 2 * a.segments}
for i in 0 .. a.segments {
a.offsets[i] = a.radius + 25 * (0.5 - rand.f32())
}
}
fn (mut p Player) reset(screen V2) {
p.bullets, p.fuel = p.bullets_limit, p.fuel_limit
p.pos, p.vel = screen.div_scalar(2), V2{0, 0}
p.radius, p.rotation = 15, -90
p.active = true
}
fn (mut game Game) handle_input() {
mut p := &game.player
p.is_engine_on = false
if game.gg.is_key_down(.escape) {
exit(0)
}
if !p.active {
return
}
game.is_up = game.gg.is_key_down(.up) || game.gg.is_key_down(.w)
is_fire := game.gg.is_key_down(.space)
is_left := game.gg.is_key_down(.left) || game.gg.is_key_down(.a)
is_right := game.gg.is_key_down(.right) || game.gg.is_key_down(.d)
if is_fire && p.cooldown <= 0 && p.active {
game.add_bullet()
p.cooldown = p.cooldown_frames
}
if p.fuel >= 10 {
if game.is_up && p.active {
angle := rads(p.rotation)
p.vel += V2{0, 0}.offset(angle, 0.1)
p.fuel -= 10
p.is_engine_on = true
}
}
if p.fuel >= 1 {
if is_left {
p.rotation -= 2
p.fuel--
}
if is_right {
p.rotation += 2
p.fuel--
}
}
}
fn (mut game Game) update() {
game.msg.frames = int_max(0, game.msg.frames - 1)
mut p := &game.player
p.cooldown = int_max(0, p.cooldown - 1)
p.pos += p.vel
p.pos.wrap(game.screen)
for mut b in game.bullets {
if !b.active {
continue
}
b.pos += b.vel
if b.pos.x < 0 || b.pos.x > game.screen.x || b.pos.y < 0 || b.pos.y > game.screen.y {
b.active = false
}
}
for mut a in game.asteroids {
if !a.active {
continue
}
a.pos += a.vel
a.pos.wrap(game.screen)
a.rotation += a.rotation_step
if p.active && p.pos.distance(a.pos) <= (p.radius + a.radius) {
// player/asteroids collision
p.active = false
a.active = false
game.split_asteroid(a, p.vel.mul_scalar(0.5))
game.player.reset(game.screen)
game.score += 50
game.show_message(text: 'Your ship was destroyed.', color: gg.red, frames: 90)
game.ships--
if game.ships <= 0 {
game.ships = 5
game.score = 0
game.asteroids = []
game.add_asteroids(10)
}
}
}
for mut b in game.bullets {
if !b.active {
continue
}
for mut a in game.asteroids {
if !a.active {
continue
}
if b.active && b.pos.distance(a.pos) <= (b.radius + a.radius) {
// bullet/asteroid collision
b.active = false
a.active = false
game.score += 100
game.split_asteroid(a, b.vel.mul_scalar(0.2))
}
}
}
if game.bullets.any(!it.active) {
game.bullets = game.bullets.filter(it.active)
}
if game.asteroids.any(!it.active) {
game.asteroids = game.asteroids.filter(it.active)
}
if game.asteroids.len == 0 {
game.level++
game.ships++
game.player.reset(game.screen)
game.add_asteroids(10)
game.show_message(text: 'YOU WIN', color: gg.green, frames: 90)
}
game.highscore = int_max(game.score, game.highscore)
}
fn (mut game Game) show_message(params Message) {
game.msg = params
}
fn (mut game Game) split_asteroid(a &Asteroid, vel V2) {
if a.radius < 30 {
return
}
shrink_factor := 0.5 + 0.3 * rand.f32()
mut a1 := Asteroid{
...*a
active: true
radius: a.radius * shrink_factor
rotation_step: -a.rotation_step
}
mut a2 := Asteroid{
...*a
active: true
radius: a.radius * (1 - shrink_factor)
rotation_step: 2 * a.rotation_step
}
a1.vel = a1.vel.mul_scalar(shrink_factor) * vel
a2.vel = a2.vel.mul_scalar(1 - shrink_factor) * (V2{0, 0} - vel)
a1.setup()
a2.setup()
game.asteroids << a1
game.asteroids << a2
}
fn (mut game Game) draw() {
ws := gg.window_size()
game.screen = V2{ws.width, ws.height}
game.gg.draw_rect_filled(0, 0, game.screen.x, game.screen.y, gg.rgba(20, 20, 20, 255))
game.draw_ship()
for b in game.bullets {
game.gg.draw_circle_filled(b.pos.x, b.pos.y, 3, gg.yellow)
}
for mut a in game.asteroids {
game.draw_asteroid(mut a)
}
scenter := game.screen.div_scalar(2)
label1 := 'Level: ${game.level} Ships: ${game.ships}'
game.gg.draw_text(5, 10, label1, size: 20, color: gg.white, align: .left)
label2 := 'B: ${game.player.bullets:02} F: ${game.player.fuel:04}'
game.gg.draw_text(int(scenter.x), 10, label2, size: 20, color: gg.green, align: .center)
label3 := 'Score: ${game.score} Highscore: ${game.highscore}'
game.gg.draw_text(int(game.screen.x) - 5, 10, label3, size: 20, color: gg.white, align: .right)
if game.msg.frames > 0 {
game.gg.draw_text(int(scenter.x), int(scenter.y / 2), game.msg.text,
size: game.msg.size
color: game.msg.color
align: game.msg.align
)
}
label4 := 'Use arrows + space to control your ship. Use Escape to end the game.'
game.gg.draw_text(int(game.screen.x - 5), int(game.screen.y - 20), label4,
size: 16
color: gg.gray
align: .right
)
}
fn (game &Game) draw_ship() {
mut p := &game.player
if !p.active {
return
}
angle := rads(p.rotation)
p1 := p.pos.offset(angle, p.radius)
p2 := p.pos.offset(angle + 2.5, 0.6 * p.radius)
p3 := p.pos.offset(angle, -0.3 * p.radius)
p4 := p.pos.offset(angle - 2.5, 0.6 * p.radius)
p.points[0] = p1.x
p.points[1] = p1.y
p.points[2] = p2.x
p.points[3] = p2.y
p.points[4] = p3.x
p.points[5] = p3.y
p.points[6] = p4.x
p.points[7] = p4.y
game.gg.draw_convex_poly(p.points, gg.white)
if p.is_engine_on {
engine := p.pos.offset(angle + math.pi, 0.7 * p.radius)
game.gg.draw_circle_filled(engine.x, engine.y, 6, gg.yellow)
}
}
fn (mut game Game) draw_asteroid(mut a Asteroid) {
if !a.active {
return
}
game.gg.draw_circle_filled(a.pos.x, a.pos.y, a.radius, gg.rgba(235, 235, 255, 215))
for i in 0 .. a.segments {
angle := rads(a.rotation + f32(i) * 360 / a.segments)
p := a.pos.offset(angle, a.offsets[i])
a.points[i * 2], a.points[i * 2 + 1] = p.x, p.y
}
game.gg.draw_convex_poly(a.points, gg.rgba(155, 155, 148, 245))
}
fn (mut game Game) add_bullet() {
if game.player.bullets <= 0 {
return
}
game.player.bullets--
angle := rads(game.player.rotation)
game.bullets << Bullet{
pos: game.player.pos
radius: 3
vel: game.player.vel.offset(angle, 10)
active: true
}
}
fn (mut game Game) add_asteroids(count int) {
for _ in 0 .. count {
mut npos := V2{}
new_asteroid_loop: for {
npos = V2.random(game.screen)
for a in game.asteroids {
if a.pos.distance(npos) < (a.radius + 30) {
continue new_asteroid_loop
}
}
if game.player.pos.distance(npos) < 5 * (game.player.radius + 30) {
continue new_asteroid_loop
}
break
}
radius := 50 + 50 * (0.5 - rand.f32())
mut asteroid := Asteroid{
pos: npos
vel: (V2.random(game.screen) - V2.random(game.screen)) / game.screen
radius: radius
rotation: 360 * rand.f32()
rotation_step: 2 * (0.5 - rand.f32())
active: true
}
asteroid.setup()
game.asteroids << asteroid
}
}
fn on_frame(mut game Game) {
if game.gg.timer.elapsed().milliseconds() > 15 {
game.gg.timer.restart()
game.handle_input()
game.update()
}
game.gg.begin()
game.draw()
game.gg.end()
}
fn main() {
mut game := &Game{}
mut fpath := asset.get_path('../assets', 'fonts/RobotoMono-Regular.ttf')
game.player.reset(game.screen)
game.add_asteroids(10)
game.gg = gg.new_context(
window_title: 'V Asteroids'
width: int(game.screen.x)
height: int(game.screen.y)
frame_fn: on_frame
user_data: game
sample_count: 2
font_path: fpath
)
game.gg.run()
}
+146
View File
@@ -0,0 +1,146 @@
// Binary Search Tree example by @SleepyRoy
struct Empty {}
struct Node[T] {
value T
left Tree[T]
right Tree[T]
}
type Tree[T] = Empty | Node[T]
// return size(number of nodes) of BST
fn (tree Tree[T]) size[T]() int {
return match tree {
Empty { 0 }
Node[T] { 1 + tree.left.size() + tree.right.size() }
}
}
// insert a value to BST
fn (tree Tree[T]) insert[T](x T) Tree[T] {
return match tree {
Empty {
Node[T]{x, tree, tree}
}
Node[T] {
if x == tree.value {
tree
} else if x < tree.value {
Node[T]{
...tree
left: tree.left.insert(x)
}
} else {
Node[T]{
...tree
right: tree.right.insert(x)
}
}
}
}
}
// whether able to find a value in BST
fn (tree Tree[T]) search[T](x T) bool {
return match tree {
Empty {
false
}
Node[T] {
if x == tree.value {
true
} else if x < tree.value {
tree.left.search(x)
} else {
tree.right.search(x)
}
}
}
}
// find the minimal value of a BST
fn (tree Tree[T]) min[T]() T {
return match tree {
Empty {
T(1e9)
}
Node[T] {
if tree.value < tree.left.min() {
tree.value
} else {
tree.left.min()
}
}
}
}
// delete a value in BST (if nonexistent do nothing)
fn (tree Tree[T]) delete[T](x T) Tree[T] {
return match tree {
Empty {
tree
}
Node[T] {
if tree.left !is Empty && tree.right !is Empty {
if x < tree.value {
Node[T]{
...tree
left: tree.left.delete(x)
}
} else if x > tree.value {
Node[T]{
...tree
right: tree.right.delete(x)
}
} else {
Node[T]{
...tree
value: tree.right.min()
right: tree.right.delete(tree.right.min())
}
}
} else if tree.left !is Empty {
if x == tree.value {
tree.left
} else {
Node[T]{
...tree
left: tree.left.delete(x)
}
}
} else {
if x == tree.value {
tree.right
} else {
Node[T]{
...tree
right: tree.right.delete(x)
}
}
}
}
}
}
fn main() {
mut tree := Tree[f64](Empty{})
vals := [0.2, 0.0, 0.5, 0.3, 0.6, 0.8, 0.9, 1.0, 0.1, 0.4, 0.7]
for i in vals {
tree = tree.insert(i)
}
println('[1] after insertion tree size is ${tree.size()}') // 11
del_vals := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5]
for i in del_vals {
tree = tree.delete(i)
}
print('[2] after deletion tree size is ${tree.size()}, ') // 7
print('and these elements were deleted: ') // 0.0 0.3 0.6 1.0
for i in vals {
if !tree.search(i) {
print('${i} ')
}
}
println('')
}
+165
View File
@@ -0,0 +1,165 @@
import os
import term
// For a more detailed description of the brainfuck language, see:
// https://en.wikipedia.org/wiki/Brainfuck
// http://brainfuck.org/brainfuck.html ,
// http://brainfuck.org/epistle.html ,
// http://www.hevanet.com/cristofd/brainfuck/ .
const show_state = os.getenv('VERBOSE') != ''
struct BFState {
mut:
pc u16 // program counter (PC) register
address u16 // a 16-bit address register, serving as an index to the memory below
program string // the BF program
memory []u8 = []u8{len: 65536} // we have 2^16 bytes of memory
targets map[int]int // a mapping for the program address of a `[` to its corresponding `]`, and from a `]` to its corresponding opening `[`.
}
fn BFState.new(program string) &BFState {
mut state := &BFState{
program: program
}
state.find_matching_pairs()
return state
}
// show the current state of an BF interpreter. Useful for debugging.
fn (state &BFState) show(suffix string) {
mut max_non_zero_address := -1
for i := state.memory.len - 1; i >= 0; i-- {
if state.memory[i] != 0 {
max_non_zero_address = i
break
}
}
println('PC: ${state.pc:04} | Address: ${state.address:04} | Memory: ${state.memory#[0..
max_non_zero_address + 1]:-40s} | Memory[Address]: ${state.memory#[state.address..
state.address + 1]:-10s} | ${suffix}')
}
// find_matching_pairs fills in the `targets` mapping for all pairs of `[` and `]`,
// so that when interpreting, we would not have to search for them anymore.
fn (mut state BFState) find_matching_pairs() {
mut stack := []int{}
for i in 0 .. state.program.len {
pi := state.program[i]
match pi {
`[` {
stack << i
}
`]` {
if stack.len == 0 {
eprintln('> unmatched `]` found in the program, at position: ${i}')
eprintln('program so far:')
eprintln(state.program#[0..i + 1])
exit(1)
}
pc := stack.pop()
state.targets[pc] = i + 1
state.targets[i] = pc + 1
// eprintln('>>> found `[` at i ${i}; pc: ${pc}')
}
else {}
}
}
if stack.len > 0 {
eprintln('> found ${stack.len} unmatched `[`:')
for i in stack {
eprintln(' `[` at position: ${i}, program so far: `${state.program#[0..i + 1]}`')
}
exit(1)
}
}
@[noreturn]
fn (state &BFState) panic_for_bracket(b1 rune, b2 rune) {
panic('unbalanced `${b1}` found, its target `${b2}` is not known; address: ${state.address}, pc: ${state.pc}')
}
fn (mut state BFState) run() ? {
mut i := 0
// the BF interpreter starts here:
for state.pc < state.program.len {
// get the current program character (corresponding to our program counter), and interpret it according to BF's rules:
instruction := state.program[state.pc]
$if trace_execution ? {
state.show('instruction ${i:08}: ${instruction:02x}, ${rune(instruction)}')
}
match instruction {
`>` {
state.address++ // increment the address
}
`<` {
state.address-- // decrement the address
}
`+` {
state.memory[state.address]++ // increment the value at the address
}
`-` {
state.memory[state.address]-- // decrement the value at the address
}
`.` {
print(rune(state.memory[state.address])) // print the value at the address
flush_stdout() // ensure that even single characters are printed immediately, and not buffered
}
`,` {
inp := u8(term.utf8_getchar() or { 0 }) // read a character value from the standard input/terminal
state.memory[state.address] = inp
}
`[` {
if state.memory[state.address] == 0 {
state.pc = u16(state.targets[state.pc])
continue
}
}
`]` {
if state.memory[state.address] != 0 {
state.pc = u16(state.targets[state.pc])
continue
}
}
`#` {
state.show('#')
}
else {
// The interpreter should ignore characters that are not part of the language.
// I.e. they are treated like programmer comments.
}
}
i++
// increment the program counter to go to the next instruction
state.pc++
// go back to the line `for state.pc < state.program.len {`
}
}
@[noreturn]
fn show_usage() {
eprintln('you need to supply a brainfuck program/expression as a string argument,')
eprintln('or filename.b, if it is located in a file (note the `.b` or `.bf` extension).')
exit(1) // exit with non-zero exit code if there is no program to run
}
fn main() {
if os.args.len < 2 {
show_usage()
}
mut program := os.args[1] // our program is fed in as a string
if program.ends_with('.b') || program.ends_with('.bf') {
program = os.read_file(program) or {
eprintln('error reading file ${program}: ${err}')
show_usage()
}
}
mut state := BFState.new(program)
state.run()
if show_state {
state.show('FINAL')
}
}
+3
View File
@@ -0,0 +1,3 @@
breakout
breakout.js
breakout.wasm
+20
View File
@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset=utf-8>
<meta content="text/html; charset=utf-8" http-equiv=Content-Type>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>V Breakout</title>
</head>
<body>
<h1>V Breakout demo</h1>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1" width="600" height="800"></canvas>
<script type='text/javascript'>
var canvasElement = document.getElementById('canvas');
var Module = {
print(...args) { console.log(...args); },
};
</script>
<script src=breakout.js></script>
</body>
</html>
+331
View File
@@ -0,0 +1,331 @@
// vtest build: !openbsd
import gg
import math
import rand
import sokol.audio
import os.asset
import sokol.sgl
const designed_width = 600
const designed_height = 800
const brick_width = 53
const brick_height = 20
const bevel_size = int(brick_height * 0.18)
const highlight_color = gg.rgba(255, 255, 255, 65)
const shade_color = gg.rgba(0, 0, 0, 65)
struct Brick {
mut:
x f32
y f32
w f32 = brick_width
h f32 = brick_height
c gg.Color
value int
alive bool = true
}
struct Game {
mut:
width int = designed_width
height int = designed_height
ball_x f32
ball_y f32
ball_r f32 = 10.0
ball_dx f32 = 4
ball_dy f32 = -4
paddle_x f32 = 250
paddle_w f32 = 100
paddle_h f32 = 20
paddle_dx f32 = 8
bricks []Brick
nbricks int
npaddles int = 10
npoints int
nlevels int = 1
nevent int
sound SoundManager
ctx &gg.Context = unsafe { nil }
}
fn Game.new() &Game {
mut g := &Game{}
g.ball_x, g.ball_y = f32(g.width) / 2, f32(g.height) - g.paddle_h
g.init_bricks()
return g
}
enum SoundKind {
paddle
brick
wall
lose_ball
}
struct SoundManager {
mut:
sounds [4][]f32 // TODO: using map[SoundKind][]f32 here breaks emscripten; use map after the fix
initialised bool
}
fn (mut sm SoundManager) init() {
all_kinds := [SoundKind.paddle, .brick, .wall, .lose_ball]!
sample_rate := f32(audio.sample_rate())
duration, volume := 0.09, f32(.25)
nframes := int(sample_rate * duration)
for i in 0 .. nframes {
t := f32(i) / sample_rate
sm.sounds[int(SoundKind.paddle)] << volume * math.sinf(t * 936.0 * 2 * math.pi)
sm.sounds[int(SoundKind.brick)] << volume * math.sinf(t * 432.0 * 2 * math.pi)
sm.sounds[int(SoundKind.wall)] << volume * math.sinf(t * 174.0 * 2 * math.pi)
sm.sounds[int(SoundKind.lose_ball)] << math.sinf(t * 123.0 * 2 * math.pi)
}
border_samples := 2000
for k, s := f32(0), 0; s <= border_samples; k, s = k + 1.0 / f32(border_samples), s + 1 {
rk := f32(1) - k
rs := nframes - border_samples - 1 + s
for kind in all_kinds {
sm.sounds[int(kind)][s] *= k
sm.sounds[int(kind)][rs] *= rk
}
}
sm.initialised = true
}
fn (mut g Game) play(k SoundKind) {
if g.sound.initialised {
s := g.sound.sounds[int(k)]
audio.push(s.data, s.len)
}
}
fn (mut g Game) init_bricks() {
yoffset, xoffset := f32(50 + rand.intn(100) or { 0 }), f32(0 + rand.intn(50) or { 0 })
g.bricks.clear()
g.nbricks = 0
for row in 0 .. 10 {
for col in 0 .. 10 {
g.bricks << Brick{
x: col * (brick_width + 1) + xoffset
y: row * (brick_height + 1) + yoffset
c: gg.rgb(0x40 | rand.u8(), 0x40 | rand.u8(), 0x40 | rand.u8())
value: 10 - row
}
g.nbricks++
}
}
for _ in 0 .. 5 + rand.intn(10) or { 0 } {
i := rand.intn(g.bricks.len - 1) or { 0 }
if g.bricks[i].alive {
g.bricks[i].alive = false
g.nbricks--
}
}
}
fn (mut g Game) draw() {
ws := gg.window_size()
g.ctx.begin()
sgl.push_matrix()
sgl.scale(f32(ws.width) / f32(designed_width), f32(ws.height) / f32(designed_height), 0)
g.draw_paddle()
g.draw_ball()
for brick in g.bricks {
if brick.alive {
g.draw_brick(brick)
}
}
label1 := 'Level: ${g.nlevels:02} Points: ${g.npoints:06}'
label2 := 'Bricks: ${g.nbricks:03} Paddles: ${g.npaddles:02}'
g.ctx.draw_text(5, 3, label1, size: 24, color: gg.rgb(255, 255, 255))
g.ctx.draw_text(320, 3, label2, size: 24, color: gg.rgb(255, 255, 255))
sgl.pop_matrix()
g.ctx.end()
}
fn (g &Game) draw_ball() {
g.ctx.draw_circle_filled(g.ball_x, g.ball_y, g.ball_r, gg.red)
mut ball_r_less := g.ball_r
for _ in 0 .. 3 {
ball_r_less *= 0.8
g.ctx.draw_circle_filled(g.ball_x - g.ball_r + ball_r_less, g.ball_y - g.ball_r +
ball_r_less, ball_r_less, highlight_color)
}
}
fn (g &Game) draw_paddle() {
roffset, rradius := -5, 18
g.ctx.draw_circle_filled(g.paddle_x - roffset, g.height, rradius, gg.blue)
g.ctx.draw_circle_filled(g.paddle_x + g.paddle_w + roffset, g.height, rradius, gg.blue)
g.ctx.draw_rect_filled(g.paddle_x, g.height - g.paddle_h + 2, g.paddle_w, g.paddle_h, gg.blue)
g.ctx.draw_rect_filled(g.paddle_x, g.height - g.paddle_h + 2, g.paddle_w, bevel_size,
highlight_color)
}
fn (g &Game) draw_brick(brick Brick) {
g.ctx.draw_rect_filled(brick.x, brick.y, brick.w, brick.h, brick.c)
g.ctx.draw_rect_filled(brick.x, brick.y, brick.w, bevel_size, highlight_color)
g.ctx.draw_rect_filled(brick.x, brick.y, bevel_size, brick.h - bevel_size, highlight_color)
g.ctx.draw_rect_filled(brick.x + brick.w - bevel_size, brick.y, bevel_size,
brick.h - bevel_size, shade_color)
g.ctx.draw_rect_filled(brick.x, brick.y + brick.h - bevel_size, brick.w, bevel_size,
shade_color)
}
fn (mut g Game) game_over() {
g.init_bricks()
g.npoints, g.nlevels, g.npaddles = 0, 1, 5
}
fn (mut g Game) goto_next_level() {
g.init_bricks()
g.npaddles++
g.nlevels++
}
fn (mut g Game) move(k f32) {
if k < 0 {
if g.paddle_x <= 0 {
return
}
} else if k > 0 {
if g.paddle_x >= g.width - g.paddle_w {
return
}
}
g.paddle_x += k * g.paddle_dx
}
fn (mut g Game) update() {
if g.ctx.pressed_keys[gg.KeyCode.left] {
g.move(-1.0)
}
if g.ctx.pressed_keys[gg.KeyCode.right] {
g.move(1.0)
}
//
g.ball_x, g.ball_y = g.ball_x + g.ball_dx, g.ball_y + g.ball_dy
// Wall collisions
if g.ball_x < g.ball_r || g.ball_x > g.width - g.ball_r {
g.ball_dx *= -1
g.play(.wall)
}
if g.ball_y < g.ball_r {
g.ball_dy *= -1
g.play(.wall)
}
if g.ball_y > g.height {
g.ball_x, g.ball_y = g.paddle_x + g.paddle_w / 2, f32(g.height) - g.paddle_h
g.ball_dy = -4
g.npaddles--
g.play(.lose_ball)
if g.npaddles <= 0 {
g.game_over()
}
}
// Paddle collision
is_ball_on_paddle_y := g.ball_y + g.ball_r > g.height - g.paddle_h
&& g.ball_y < g.height - g.ball_r
is_ball_on_paddle_x := g.ball_x > g.paddle_x - 10 && g.ball_x < g.paddle_x + g.paddle_w + 10
if is_ball_on_paddle_y && is_ball_on_paddle_x {
g.play(.paddle)
g.ball_dy = -math.abs(g.ball_dy)
x_in_paddle := g.ball_x - g.paddle_x
rmargin := 10
if x_in_paddle < rmargin || x_in_paddle + rmargin > g.paddle_w {
g.ball_dx *= -1
} else if !(x_in_paddle > 40 && x_in_paddle < 60) {
r := 10 * (-0.5 + rand.f32())
g.ball_dx += r
g.ball_dx = f32(int_min(int_max(-80, int(g.ball_dx * 10)), 80)) / 10
}
}
// Brick collisions
for mut brick in g.bricks {
if brick.alive && g.ball_y - g.ball_r < brick.y + brick.h && g.ball_y + g.ball_r > brick.y
&& g.ball_x + g.ball_r > brick.x && g.ball_x - g.ball_r < brick.x + brick.w {
g.play(.brick)
brick.alive = false
g.nbricks--
g.npoints += brick.value
g.ball_dy *= -1
if g.nbricks == 0 {
g.goto_next_level()
}
}
}
}
fn (mut g Game) touch_event(touch_point gg.TouchPoint) {
ws := gg.window_size()
tx := touch_point.pos_x
if tx <= f32(ws.width) * 0.5 {
g.move(-1.0)
} else {
g.move(1.0)
}
}
@[if wasm32_emscripten]
fn (mut g Game) handle_event() {
if g.nevent > 0 {
return
}
// the audio has to be started when the wasm canvas has received user
// interaction, unlike on desktop platforms
audio.setup(buffer_frames: 1024)
g.sound.init()
g.nevent++
}
fn main() {
mut g := Game.new()
mut fpath := asset.get_path('../assets', 'fonts/RobotoMono-Regular.ttf')
$if !wasm32_emscripten {
audio.setup(buffer_frames: 512) // too small values lead to cracking sounds or no sound at all on macos
g.sound.init()
fpath = ''
}
g.ctx = gg.new_context(
width: g.width
height: g.height
window_title: 'V Breakout'
frame_fn: fn (mut g Game) {
dt := g.ctx.timer.elapsed().milliseconds()
if dt > 15 {
g.update()
g.ctx.timer.restart()
}
g.draw()
}
click_fn: fn (x f32, y f32, btn gg.MouseButton, mut g Game) {
g.handle_event()
}
event_fn: fn (e &gg.Event, mut g Game) {
g.handle_event()
if e.typ == .touches_began || e.typ == .touches_moved {
if e.num_touches > 0 {
touch_point := e.touches[0]
g.touch_event(touch_point)
}
}
}
keydown_fn: fn (key gg.KeyCode, _ gg.Modifier, mut g Game) {
g.handle_event()
match key {
.r {
g.game_over()
}
.escape {
exit(0)
}
else {}
}
}
user_data: g
font_path: fpath
)
g.ctx.run()
}
+28
View File
@@ -0,0 +1,28 @@
import datatypes
struct KeyVal {
mut:
key int
val int
}
fn (a KeyVal) == (b KeyVal) bool {
return a.key == b.key
}
fn (a KeyVal) < (b KeyVal) bool {
return a.key < b.key
}
fn main() {
mut bst := datatypes.BSTree[KeyVal]{}
bst.insert(KeyVal{ key: 1, val: 12 })
println(bst.in_order_traversal())
bst.insert(KeyVal{ key: 2, val: 34 })
bst.insert(KeyVal{ key: -2, val: 203 })
for elem in bst.in_order_traversal() {
println(elem.val)
}
}
+19
View File
@@ -0,0 +1,19 @@
// Simple raw HTTP head request
import net
import time
import io
fn main() {
// Make a new connection
mut conn := net.dial_tcp('google.com:80')!
// Simple http HEAD request for a file
conn.write_string('GET /index.html HTTP/1.0\r\n\r\n')!
// Wrap in a buffered reader
mut r := io.new_buffered_reader(reader: conn)
for {
l := r.read_line() or { break }
println('${l}')
// Make it nice and obvious that we are doing this line by line
time.sleep(100 * time.millisecond)
}
}
+6
View File
@@ -0,0 +1,6 @@
# Files and directories made by build.vsh:
/target/
/test.txt
# Pre-compiled build.vsh
/build
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env -S v run
import build
import time
// Define variables that can be used to change tasks in the buildscript
const app_name = 'hello'
const program_args = 'World'
const build_dir = 'target'
// Make the build context
mut context := build.context(
// Set the default task to `release` when no arguments are provided
default: 'release'
)
// Add a few simple tasks
context.task(name: 'doc', run: |self| system('echo "Nothing to do"'))
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 -o ${app_name} .'))
// `_` to denote "private" tasks. Nothing stops the user from using it, but
// this tells them that the task is not meant to be used by them.
context.task(
name: '_mkdirs'
// The `help` field is displayed in `--tasks` to give a short summary of what the task does.
help: 'Makes the directories used by the application'
run: fn (self build.Task) ! {
if !exists(build_dir) {
mkdir_all(build_dir) or { panic(err) }
}
}
)
// This task will only run when the `test.txt` file is outdated
context.artifact(
name: 'test.txt'
help: 'Generate test.txt'
run: fn (self build.Task) ! {
write_file('test.txt', time.now().str())!
}
)
// Add a more complex task
context.task(
name: 'release'
help: 'Build the app in production mode, generates documentation, and releases the build on Git'
depends: ['_mkdirs', 'doc', 'test.txt']
run: fn (self build.Task) ! {
system('v -prod -o ${build_dir}/${app_name} .')
// Pretend we are using Git to publish the built file as a release here.
}
)
// Run the build context. This will iterate over os.args and each corresponding
// task, skipping any arguments that start with a hyphen (-)
context.run()
+5
View File
@@ -0,0 +1,5 @@
import os
fn main() {
println('Hello, ${os.args[1]}!')
}
+98
View File
@@ -0,0 +1,98 @@
import os
// Example of C interop for a very handy task.
//
// wkhtmltopdf and wkhtmltoimage are open source (LGPLv3) command line tools to
// render HTML into PDF and various image formats using the Qt WebKit rendering
// engine. These run entirely "headless" and do not require a display or display
// service.
//
// https://github.com/wkhtmltopdf/wkhtmltopdf
// https://wkhtmltopdf.org/downloads.html
// https://wkhtmltopdf.org/libwkhtmltox/
#flag -lwkhtmltox
#include "wkhtmltox/pdf.h" # You can install the C package for your system from the wkhtmltopdf.org/downloads.html page
pub struct C.wkhtmltopdf_global_settings {}
pub struct C.wkhtmltopdf_object_settings {}
pub struct C.wkhtmltopdf_converter {}
fn C.wkhtmltopdf_init(use_graphics bool) i32
fn C.wkhtmltopdf_deinit() i32
fn C.wkhtmltopdf_version() &char
fn C.wkhtmltopdf_create_global_settings() &C.wkhtmltopdf_global_settings
fn C.wkhtmltopdf_destroy_global_settings(global_settings &C.wkhtmltopdf_global_settings)
fn C.wkhtmltopdf_set_global_setting(global_settings &C.wkhtmltopdf_global_settings, name &char, value &char) bool
fn C.wkhtmltopdf_create_object_settings() &C.wkhtmltopdf_object_settings
fn C.wkhtmltopdf_destroy_object_settings(object_settings &C.wkhtmltopdf_object_settings)
fn C.wkhtmltopdf_set_object_setting(object_settings &C.wkhtmltopdf_object_settings, name &char, value &char) bool
fn C.wkhtmltopdf_create_converter(global_settings &C.wkhtmltopdf_global_settings) &C.wkhtmltopdf_converter
fn C.wkhtmltopdf_destroy_converter(converter &C.wkhtmltopdf_converter)
fn C.wkhtmltopdf_add_object(converter &C.wkhtmltopdf_converter, object_settings &C.wkhtmltopdf_object_settings,
data &char)
fn C.wkhtmltopdf_convert(converter &C.wkhtmltopdf_converter) bool
fn C.wkhtmltopdf_http_error_code(converter &C.wkhtmltopdf_converter) i32
fn C.wkhtmltopdf_get_output(converter &C.wkhtmltopdf_converter, data &&char) i32
fn main() {
// init
init := C.wkhtmltopdf_init(0)
println('wkhtmltopdf_init: ${init}')
version := unsafe { cstring_to_vstring(&char(C.wkhtmltopdf_version())) }
println('wkhtmltopdf_version: ${version}')
global_settings := C.wkhtmltopdf_create_global_settings()
println('wkhtmltopdf_create_global_settings: ${voidptr(global_settings)}')
object_settings := C.wkhtmltopdf_create_object_settings()
println('wkhtmltopdf_create_object_settings')
converter := C.wkhtmltopdf_create_converter(global_settings)
println('wkhtmltopdf_create_converter: ${voidptr(converter)}')
// convert
mut result := C.wkhtmltopdf_set_object_setting(object_settings, c'page',
c'http://www.google.com.br')
println('wkhtmltopdf_set_object_setting: ${result} [page = http://www.google.com.br]')
C.wkhtmltopdf_add_object(converter, object_settings, 0)
println('wkhtmltopdf_add_object')
result = C.wkhtmltopdf_convert(converter)
println('wkhtmltopdf_convert: ${result}')
error_code := C.wkhtmltopdf_http_error_code(converter)
println('wkhtmltopdf_http_error_code: ${error_code}')
if result {
pdata := &char(unsafe { nil })
ppdata := &pdata
nbytes := C.wkhtmltopdf_get_output(converter, voidptr(ppdata))
println('wkhtmltopdf_get_output: ${nbytes} bytes')
mut file := os.open_file('./google.pdf', 'w+', 0o666) or {
println('ERR: ${err}')
return
}
wrote := unsafe { file.write_ptr(pdata, nbytes) }
println('write_bytes: ${wrote} [./google.pdf]')
file.flush()
file.close()
}
// destroy
C.wkhtmltopdf_destroy_converter(converter)
println('wkhtmltopdf_destroy_converter')
C.wkhtmltopdf_destroy_object_settings(object_settings)
println('wkhtmltopdf_destroy_object_settings: ${voidptr(object_settings)}')
C.wkhtmltopdf_destroy_global_settings(global_settings)
println('wkhtmltopdf_destroy_global_settings')
deinit := C.wkhtmltopdf_deinit()
println('wkhtmltopdf_deinit: ${deinit}')
}
+7
View File
@@ -0,0 +1,7 @@
#include <stdio.h>
fn C.puts(&char) i32
fn main() {
C.puts(c'V can call C functions like `puts` too.')
}
@@ -0,0 +1,2 @@
# Ignore the generated C files in this directory
*.c
+54
View File
@@ -0,0 +1,54 @@
## A simple example to show how to call a function written in v from c
### Compile as a shared library
#### On Linux:
Step 1: Compile the v code to a shared library using `v -cc gcc -shared v_test_print.v` or
`v -cc gcc -shared v_test_math.v`.
Step 2: Compile the c file using `gcc test_print.c v_test_print.so -o test_print -Wl,-rpath=.` or
`gcc test_math.c v_test_math.so -o test_math -Wl,-rpath=.`.
Step 3: Run the compiled c file using `./test_print` or `./test_math`.
#### On Mac OSX:
On Mac OSX, libgc can be obtained from homebrew by `brew install libgc`.
During compiling and/or linking, `-I/usr/local/include -L/usr/local/lib` (for x86_64),
or `-I/opt/homebrew/include -L/opt/homebrew/lib` (for arm64) can be added depending the arch.
Step 1: Compile the v code to a shared library using `v -cc gcc -shared v_test_print.v` or
`v -cc gcc -shared v_test_math.v`.
Step 2: Compile the c file using `gcc test_print.c v_test_print.dylib -o test_print` or
`gcc test_math.c v_test_math.dylib -o test_math`.
Step 3: Run the compiled c file using `LD_LIBRARY_PATH=. ./test_print` or
`LD_LIBRARY_PATH=. ./test_math`.
#### On Windows:
Step 1: Compile the v code to a shared library using `v -cc gcc -shared v_test_print.v` or
`v -cc gcc -shared v_test_math.v`.
Step 2: Compile the c file using `gcc test_print.c v_test_print.dll -o test_print.exe` or
`gcc test_math.c v_test_math.dll -o test_math.exe`.
Step 3: Run the compiled c file using `test_print.exe` or `test_math.exe`.
### Compile as a c file
***Requirements: `libgc` must be installed***
Step 1: Compile the v code to a shared library using
`v -shared -cc gcc -o v_test_print.c v_test_print.v` or
`v -shared -cc gcc -o v_test_math.c v_test_math.v`.
*Specifying the output with a `.c` extension will generate the corresponding C source file.*
Step 2: Compile the c file using `gcc test_print.c v_test_print.c -o test_print -lgc` or
`gcc test_math.c v_test_math.c -o test_math -lgc -lm`.
Step 3: Run the compiled c file using `./test_print` or `./test_math`.
@@ -0,0 +1,14 @@
// vtest build: false // Check the README.md for detailed information; this file needs special compilation options
module test_math
import math
@[export: 'square']
fn calculate_square(i int) int {
return i * i
}
@[export: 'sqrt_of_sum_of_squares']
fn calculate_sqrt_of_sum_of_squares(x f64, y f64) f64 {
return math.sqrt(x * x + y * y)
}
@@ -0,0 +1,7 @@
// vtest build: false // Check the README.md for detailed information; this file needs special compilation options
module test_print
@[export: 'foo']
fn show_foo(s &char) {
println(unsafe { cstring_to_vstring(s) })
}
@@ -0,0 +1,7 @@
A simple example to show how to call a function written in v from python
Step 1: Compile the v code to a shared library using `v -d no_backtrace -shared test.v`
Step 2: Run the python file using `python3 test.py`
Note: you do not need `-d no_backtrace` if you use gcc or clang .
@@ -0,0 +1,26 @@
from ctypes import *
import math, os
## Load the V shared library:
so_file="./test.so"
if os.name=="nt":
so_file="./test.dll"
lib = CDLL(so_file)
## Pass an integer to a V function, and receiving back an integer:
print("lib.square(10) result is", lib.square(10))
assert lib.square(10) == 100, "Cannot validate V square()."
## Pass a floating point number to a V function:
lib.sqrt_of_sum_of_squares.restype = c_double
assert lib.sqrt_of_sum_of_squares(c_double(1.1), c_double(2.2)) == math.sqrt(1.1*1.1 + 2.2*2.2), "Cannot validate V sqrt_of_sum_of_squares()."
## Passing a V string to a V function, and receiving back a V string:
class VString(Structure):
_fields_ = [("str", c_char_p), ("len", c_int)]
lib.process_v_string.argtypes = [VString]
lib.process_v_string.restype = VString
assert lib.process_v_string(VString(b'World', 5)).str == b'v World v'
print('Hello', str(lib.process_v_string(VString(b'World', 5)).str, 'utf-8'))
@@ -0,0 +1,21 @@
// vtest build: present_python? // the example only makes sense to be compiled, when python is installed
module test
// Note: compile this with `v -d no_backtrace -shared test.v`
import math
@[export: 'square']
fn square(i int) int {
return i * i
}
@[export: 'sqrt_of_sum_of_squares']
fn sqrt_of_sum_of_squares(x f64, y f64) f64 {
return math.sqrt(x * x + y * y)
}
// you do not have to use the same name in the export attribute
@[export: 'process_v_string']
fn work(s string) string {
return 'v ${s} v'
}
@@ -0,0 +1,7 @@
A simple example to show how to call a function written in v from ruby
Step 1: Compile the v code to a shared library using `v -d no_backtrace -shared test.v`
Step 2: Run the ruby file using `ruby test.rb`
Note: you do not need `-d no_backtrace` if you use gcc or clang .
+39
View File
@@ -0,0 +1,39 @@
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'ffi'
end
require 'ffi'
# extension for shared libraries varies by platform - see vlib/dl/dl.v
# get_shared_library_extension()
def shared_library_extension
if Gem.win_platform?
'.dll'
elsif RUBY_PLATFORM =~ /darwin/ # MacOS
'.dylib'
else
'.so'
end
end
module Lib
extend FFI::Library
begin
ffi_lib File.join(File.dirname(__FILE__), 'test' + shared_library_extension)
rescue LoadError
abort("No shared library test#{shared_library_extension} found. Check examples/call_v_from_ruby/README.md")
end
attach_function :square, [:int], :int
attach_function :sqrt_of_sum_of_squares, [:double, :double], :double
end
puts "Lib.square(10) result is #{Lib.square(10)}"
raise 'Cannot validate V square().' unless Lib.square(10) == 100
raise 'Cannot validate V sqrt_of_sum_of_squares().' unless \
Lib.sqrt_of_sum_of_squares(1.1, 2.2) == Math.sqrt(1.1*1.1 + 2.2*2.2)
+14
View File
@@ -0,0 +1,14 @@
// vtest build: present_ruby? // the example only makes sense to be compiled, when ruby is installed
module test
import math
@[export: 'square']
fn square(i int) int {
return i * i
}
@[export: 'sqrt_of_sum_of_squares']
fn sqrt_of_sum_of_squares(x f64, y f64) f64 {
return math.sqrt(x * x + y * y)
}
+78
View File
@@ -0,0 +1,78 @@
module main
import encoding.cbor
import encoding.hex
import time
struct Address {
street string
city string
zip string @[cbor: 'postal_code']
}
struct User {
name string
age u32
email ?string
tags []string
address Address
signed_up time.Time
internal string @[skip]
}
fn main() {
user := User{
name: 'Alice'
age: 30
email: 'alice@example.com'
tags: ['admin', 'beta']
address: Address{
street: '1 Test Lane'
city: 'Paris'
zip: '75000'
}
signed_up: time.parse_iso8601('2025-01-15T10:00:00Z') or { time.now() }
internal: 'will not be encoded'
}
// 1. Generic typed encode/decode
bytes := cbor.encode[User](user, cbor.EncodeOpts{})!
println('encoded ${bytes.len} bytes: ${hex.encode(bytes)}')
back := cbor.decode[User](bytes, cbor.DecodeOpts{})!
println('round-trip name=${back.name} age=${back.age} city=${back.address.city}')
// 2. Canonical encoding for stable hashing/signing
mut m := map[string]int{}
m['z'] = 26
m['a'] = 1
m['m'] = 13
canonical := cbor.encode[map[string]int](m, cbor.EncodeOpts{
canonical: true
})!
println('canonical map: ${hex.encode(canonical)}')
// 3. Decode an unknown payload into a Value tree
v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{})!
if name_val := v.get('name') {
if s := name_val.as_string() {
println('peeked name from Value tree: ${s}')
}
}
// 4. Manual streaming: build a CBOR array of mixed types
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_array_header(3)
p.pack_uint(42)
p.pack_text('hello')
p.pack_bool(true)
stream_bytes := p.bytes()
println('manual stream: ${hex.encode(stream_bytes)}')
mut up := cbor.new_unpacker(stream_bytes, cbor.DecodeOpts{})
n := up.unpack_array_header()!
first := up.unpack_uint()!
second := up.unpack_text()!
third := up.unpack_bool()!
println('unpacked array of ${n}: ${first}, "${second}", ${third}')
}
+80
View File
@@ -0,0 +1,80 @@
module main
import cli { Command, Flag }
import os
fn main() {
mut cmd := Command{
name: 'cli'
description: 'An example of the cli library.'
version: '1.0.0'
}
mut greet_cmd := Command{
name: 'greet'
description: 'Prints greeting in different languages.'
usage: '<name>'
required_args: 1
pre_execute: greet_pre_func
execute: greet_func
post_execute: greet_post_func
}
greet_cmd.add_flag(Flag{
flag: .string
required: true
name: 'language'
abbrev: 'l'
description: 'Language of the message.'
})
greet_cmd.add_flag(Flag{
flag: .int
name: 'times'
default_value: ['3']
description: 'Number of times the message gets printed.'
})
greet_cmd.add_flag(Flag{
flag: .string_array
name: 'fun'
description: 'Just a dumby flags to show multiple.'
})
cmd.add_command(greet_cmd)
cmd.setup()
cmd.parse(os.args)
}
fn greet_func(cmd Command) ! {
language := cmd.flags.get_string('language') or {
panic('Failed to get `language` flag: ${err}')
}
times := cmd.flags.get_int('times') or { panic('Failed to get `times` flag: ${err}') }
name := cmd.args[0]
for _ in 0 .. times {
match language {
'english', 'en' {
println('Welcome ${name}')
}
'german', 'de' {
println('Willkommen ${name}')
}
'dutch', 'nl' {
println('Welkom ${name}')
}
else {
println('Unsupported language')
println('Supported languages are `english`, `german` and `dutch`.')
break
}
}
}
fun := cmd.flags.get_strings('fun') or { panic('Failed to get `fun` flag: ${err}') }
for f in fun {
println('fun: ${f}')
}
}
fn greet_pre_func(_cmd Command) ! {
println('This is a function running before the main function.\n')
}
fn greet_post_func(_cmd Command) ! {
println('\nThis is a function running after the main function.')
}
+122
View File
@@ -0,0 +1,122 @@
// This example demonstrates the `vlib/cli` features that show up automatically
// in `--help` when you populate the optional `group`, `examples` and
// `learn_more` fields of `Command`. No opt-in is required — leaving any of
// them empty simply skips the corresponding section.
//
// Compile from the V repo root:
// v -o tasky examples/cli_groups.v
//
// Try it out:
// ./tasky --help # root help with grouped commands and examples
// ./tasky issue --help # sub-command help with INHERITED FLAGS
// ./tasky issue list -v
// ./tasky version
module main
import cli
import os
fn main() {
mut app := cli.Command{
name: 'tasky'
description: 'A tiny issue tracker CLI'
version: '0.1.0'
posix_mode: true
examples: [
'\$ tasky issue list',
'\$ tasky issue create --title "Fix CI"',
'\$ tasky config get editor',
]
learn_more: 'Use `tasky <command> --help` for details about a command.\nDocumentation lives at https://example.test/tasky'
}
app.add_flag(cli.Flag{
flag: .string
name: 'config'
abbrev: 'c'
description: 'Path to a tasky config file'
global: true
})
app.add_command(issue_command())
app.add_command(config_command())
app.setup()
app.parse(os.args)
}
fn issue_command() cli.Command {
return cli.Command{
name: 'issue'
description: 'Work with issues'
group: 'Core commands'
commands: [
cli.Command{
name: 'list'
description: 'List open issues'
execute: issue_list
flags: [
cli.Flag{
flag: .bool
name: 'verbose'
abbrev: 'v'
description: 'Show issue bodies in addition to titles'
},
]
},
cli.Command{
name: 'create'
description: 'Create a new issue'
execute: issue_create
flags: [
cli.Flag{
flag: .string
name: 'title'
abbrev: 't'
description: 'Title of the issue to create'
required: true
},
]
},
]
}
}
fn config_command() cli.Command {
return cli.Command{
name: 'config'
description: 'Read or write tasky settings'
group: 'Additional commands'
commands: [
cli.Command{
name: 'get'
description: 'Print the value of a config key'
usage: '<key>'
required_args: 1
execute: config_get
},
cli.Command{
name: 'set'
description: 'Update a config key'
usage: '<key> <value>'
required_args: 2
execute: config_set
},
]
}
}
fn issue_list(cmd cli.Command) ! {
verbose := cmd.flags.get_bool('verbose') or { false }
println('Listing issues (verbose=${verbose})')
}
fn issue_create(cmd cli.Command) ! {
title := cmd.flags.get_string('title')!
println('Created issue: ${title}')
}
fn config_get(cmd cli.Command) ! {
println('config get ${cmd.args[0]}')
}
fn config_set(cmd cli.Command) ! {
println('config set ${cmd.args[0]} = ${cmd.args[1]}')
}
+158
View File
@@ -0,0 +1,158 @@
// An X11 clock modeled after https://en.wikipedia.org/wiki/Station_clock
// This is a small V example that was based off of the fireworks example.
// Written by Stefan Schroeder in 2021 for the v project examples.
// See LICENSE for license information.
import gg
import math
import time
// All coordinates are designed for a clock size of this many pixel.
// You cannot change the size of the clock by adjusting this value.
const design_size = 700
const center = 350
// Half the width of a tic-mark.
const tw = 9
// Height of a minute tic-mark. (hour is twice, 3-hour is thrice)
const th = 25
// Padding of tic-mark to window border
const tp = 10
const tic_color = gg.Color{
r: 50
g: 50
b: 50
}
const hand_color = gg.black
const second_hand_color = gg.red
struct App {
minutes_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw,
tp + 1 * th, center - tw, tp + 1 * th]
hours_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw,
tp + 2 * th, center - tw, tp + 2 * th]
hours3_tic []f32 = [f32(center - tw), tp, center + tw, tp, center + tw, tp, center + tw,
tp + 3 * th, center - tw, tp + 3 * th]
hour_hand []f32 = [f32(329), 161, 350, 140, 371, 161, 371, 413, 329, 413]
minute_hand []f32 = [f32(334.25), 40.25, 350, 24.5, 365.75, 40.25, 365.75, 427, 334.25, 427]
second_hand []f32 = [f32(345.8), 38.5, 350, 34.3, 354.2000, 38.5, 358.75, 427, 341.25, 427]
mut:
gg &gg.Context = unsafe { nil }
draw_flag bool = true
dpi_scale f32 = 1.0
}
fn on_frame(mut app App) {
if !app.draw_flag {
return
}
app.gg.begin()
for i in 0 .. 60 { // draw minute tics
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minutes_tic, tic_color, i * 6)
}
for i in 0 .. 12 { // hours
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hours_tic, tic_color, i * 30)
}
for i in 0 .. 4 { // 3 hours
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hours3_tic, tic_color, i * 90)
}
n := time.now()
// draw hour hand
i := f32(n.hour) + f32(n.minute) / 60.0
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.hour_hand, hand_color, i * 30)
// draw minute hand
mut j := f32(n.minute)
if n.second == 59 { // make minute hand move smoothly
j += f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0))
}
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minute_hand, hand_color, j * 6)
// draw second hand with smooth transition
k := f32(n.second) + f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0))
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.second_hand, second_hand_color,
0 + k * 6)
app.gg.end()
}
// Rotate a polygon round the centerpoint
@[manualfree]
fn draw_convex_poly_rotate(mut ctx gg.Context, dpi_scale f32, points []f32, c gg.Color, angle f32) {
sa := math.sin(math.pi * angle / 180.0)
ca := math.cos(math.pi * angle / 180.0)
mut rotated_points := []f32{cap: points.len}
for i := 0; i < points.len / 2; i++ {
x := points[2 * i]
y := points[2 * i + 1]
xn := f32((x - center) * ca - (y - center) * sa)
yn := f32((x - center) * sa + (y - center) * ca)
rotated_points << (xn + center) * dpi_scale
rotated_points << (yn + center) * dpi_scale
}
ctx.draw_convex_poly(rotated_points, c)
unsafe { rotated_points.free() }
}
fn (mut app App) resize() {
size := gg.window_size()
// avoid calls when minimized
if size.width < 2 && size.height < 2 {
return
}
w := f32(size.width) / design_size
h := f32(size.height) / design_size
app.dpi_scale = if w < h { w } else { h }
}
fn on_event(e &gg.Event, mut app App) {
match e.typ {
.resized, .resumed {
app.resize()
}
.iconified {
app.draw_flag = false
}
.restored {
app.draw_flag = true
app.resize()
}
else {
if e.typ == .key_down {
match e.key_code {
.q {
println('Good bye.')
// do we need to free anything here?
app.gg.quit()
}
else {}
}
}
}
}
}
fn on_init(mut app App) {
app.resize()
}
fn main() {
println("Press 'q' to quit.")
mut app := &App{}
app.gg = gg.new_context(
width: design_size
height: design_size
window_title: 'Clock!'
bg_color: gg.white
user_data: app
frame_fn: on_frame
event_fn: on_event
init_fn: on_init
)
app.gg.run()
}
@@ -0,0 +1,56 @@
// This example shows how to use V's compile time defines and values via the -d flag and $d() function.
//
// To change the default values below, pass compile flags to v using: `-d ident=<value>`
// Examples:
// ´´´
// v -d header -d pad=10 -d jobs=8 run d_compile_value.v
// v -d id="Bob" -d pad=2 -d pad_char='$' -d jobs=10 run d_compile_value.v
// ```
const pad = $d('pad', 5)
const pad_char = $d('pad_char', `-`)
const footer = '
Available compile time flags:
-d pad=<i64>
-d pad_char=<character>
-d id="<string>"
-d jobs=<i64>
-d header=<bool>
You can turn this message off with:
-d footer=false'
struct Job {
id string = $d('id', 'Job') // adjust with `-d id="My ID"`
}
struct App {
jobs [$d('jobs', 4)]Job // adjust fixed array size with `-d jobs=6`
}
// println_padded prints `str` to stdout with a padding.
// Padding length and padding character can be adjusted at compile time
// via `-d pad=1` and `-d pad_char=x`.
fn println_padded(str string) {
for _ in 0 .. pad {
print(rune(pad_char))
}
println(' ${str}')
}
fn main() {
header := $d('header', false) // adjust header variable with `-d header=true` or simply `-d header`
if header {
println_padded('Compile Value Example')
}
app := App{}
for i, job in app.jobs {
println_padded('${job.id} ${i + 1}')
}
// Turn the footer message off using `-d footer=false`
if $d('footer', true) {
println(footer)
}
}
+38
View File
@@ -0,0 +1,38 @@
struct App {}
fn (mut app App) method_one() {}
fn (mut app App) method_two() int {
return 0
}
fn (mut app App) method_three(s string) string {
return s
}
fn main() {
$for method in App.methods {
$if method.typ is fn (string) string {
println('${method.name} IS `fn(string) string`')
} $else {
println('${method.name} is NOT `fn(string) string`')
}
$if method.return_type !is int {
println('${method.name} does NOT return `int`')
} $else {
println('${method.name} DOES return `int`')
}
$if method.args[0].typ !is string {
println("${method.name}'s first arg is NOT `string`")
} $else {
println("${method.name}'s first arg IS `string`")
}
// TODO: Double inversion, should this even be allowed?
$if method.typ is fn () {
println('${method.name} IS a void method')
} $else {
println('${method.name} is NOT a void method')
}
println('')
}
}
+49
View File
@@ -0,0 +1,49 @@
// An example deserializer implementation
struct User {
name string
age int
}
fn main() {
data := 'name=Alice\nage=18'
user := decode[User](data)
println(user)
}
fn decode[T](data string) T {
mut result := T{}
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
$if field.typ is string {
// $(string_expr) produces an identifier
result.$(field.name) = get_string(data, field.name)
} $else $if field.typ is int {
result.$(field.name) = get_int(data, field.name)
}
}
return result
}
fn get_string(data string, field_name string) string {
for line in data.split_into_lines() {
key_val := line.split('=')
if key_val[0] == field_name {
return key_val[1]
}
}
return ''
}
fn get_int(data string, field string) int {
return get_string(data, field).int()
}
// `decode<User>` generates:
// fn decode_User(data string) User {
// mut result := User{}
// result.name = get_string(data, 'name')
// result.age = get_int(data, 'age')
// return result
// }
@@ -0,0 +1,18 @@
import time
// Simulate expensive computing using sleep function
fn expensive_computing(id int, duration int) {
println('Executing expensive computing task (${id})...')
time.sleep(duration * time.millisecond)
println('Finish task ${id} on ${duration} ms')
}
fn main() {
mut threads := []thread{}
threads << spawn expensive_computing(1, 100)
threads << spawn expensive_computing(2, 500)
threads << spawn expensive_computing(3, 1000)
// Join all tasks
threads.wait()
println('All jobs finished!')
}
@@ -0,0 +1,32 @@
import net.http
import sync
import time
fn vlang_time(mut wg sync.WaitGroup) !string {
start := time.ticks()
data := http.get('https://vlang.io/utc_now')!
finish := time.ticks()
println('Finish getting time ${finish - start} ms')
println(data.body)
wg.done()
return data.body
}
fn remote_ip(mut wg sync.WaitGroup) !string {
start := time.ticks()
data := http.get('https://api.ipify.org')!
finish := time.ticks()
println('Finish getting ip ${finish - start} ms')
println(data.body)
wg.done()
return data.body
}
fn main() {
mut wg := sync.new_waitgroup()
wg.add(2)
// Run tasks async
spawn vlang_time(mut wg)
spawn remote_ip(mut wg)
wg.wait()
}
@@ -0,0 +1,13 @@
fn expensive_computing(i int) int {
return i * i
}
fn main() {
mut threads := []thread int{}
for i in 1 .. 10 {
threads << spawn expensive_computing(i)
}
// Join all tasks
r := threads.wait()
println('All jobs finished: ${r}')
}
@@ -0,0 +1,19 @@
module main
fn main() {
th := spawn my_print1()
th.wait()
th2 := spawn my_print2()
th2.wait()
}
// default stack size
fn my_print1() {
println('hello word')
}
// 2MB stack size
@[spawn_stack: 2097152]
fn my_print2() {
println('ahoj svet')
}
Whitespace-only changes.
@@ -0,0 +1,36 @@
// vtest build: false // This should be build with: `v -use-coroutines coroutine_benchs.v`
// Note: the Photon wrapper is not yet trivial enough to build/install on the CI.
import coroutines
import time
import net.http
import sync
const run_time = 10 * time.second
fn request(mut mu sync.Mutex, count &int) {
for {
http.get('http://vlang.io/utc_now') or { panic(err) }
mu.lock()
unsafe {
(*count)++
}
mu.unlock()
}
}
fn main() {
mut mu := sync.new_mutex()
mut count := 0
for _ in 0 .. 8 {
go request(mut mu, &count)
}
$if is_coroutine ? {
println('IS COROUTINE=true')
coroutines.sleep(run_time)
} $else {
println('IS COROUTINE=false')
time.sleep(run_time)
}
println('${count} requests made.')
}
@@ -0,0 +1,62 @@
// vtest build: false // This should be build with: v -use-coroutines simple_coroutines.v
// Note: the Photon wrapper is not yet trivial enough to build/install on the CI.
import coroutines
import time
import os
import net.http
fn foo(a int) {
for {
println('1hello from foo() a=${a}')
// C.printf(c'hello from foo() a=%d\n', a)
coroutines.sleep(1 * time.second)
}
}
fn foo2(a int) {
mut i := 0
for {
println('hello from foo2() a=${a}')
// C.printf(c'hello from foo2() a=%d\n', a)
coroutines.sleep(2 * time.second)
i++
resp := http.get('https://vlang.io/utc_now') or { panic(err) }
println(resp)
mut f := os.create('/tmp/FOO2_a${i}') or { panic(err) }
f.write_string(resp.body) or { panic(err) }
f.close()
}
}
fn foo3(a int) {
for {
println('hello from foo3() a=${a}')
// C.printf(c'hello from foo3() a=%d\n', a)
coroutines.sleep(3 * time.second)
}
}
fn foo4(a int) {
for {
println('hello from foo4() a=${a}')
// C.printf(c'hello from foo4() a=%d\n', a)
coroutines.sleep(3 * time.second)
}
}
fn main() {
go foo(10)
go foo2(20)
go foo3(30)
go foo3(40)
$if is_coroutine ? {
println('IS COROUTINE=true')
} $else {
println('IS COROUTINE=false')
}
for {
println('hello from MAIN')
coroutines.sleep(1 * time.second)
}
println('done')
}
@@ -0,0 +1,63 @@
Note: To more deep study see https://en.wikibooks.org/wiki/X86_Assembly
# SSE and MMX Extensions
This document provides an overview of the SSE and MMX extensions used in the project.
## Table of Contents
- [Introduction](#introduction)
- [SSE Extensions](#sse-extensions)
- [MMX Extensions](#mmx-extensions)
- [Usage](#usage)
## Introduction
SSE (Streaming SIMD Extensions) and MMX (MultiMedia eXtensions) are instruction sets used to
enhance the performance of multimedia and signal processing applications.
## SSE Extensions
SSE extensions provide a set of instructions that can handle multiple data with a single
instruction, improving the performance of applications that require heavy mathematical
computations.
from: [wikibooks](https://en.wikibooks.org/wiki/X86_Assembly/SSE#SSE_Instruction_Set)
There are literally hundreds of SSE instructions, some of which are capable of much more than
simple SIMD arithmetic. For more in-depth references take a look at the resources chapter of this
book.
You may notice that many floating point SSE instructions end with something like PS or SD. These
suffixes differentiate between different versions of the operation. The first letter describes
whether the instruction should be Packed or Scalar. Packed operations are applied to every member
of the register, while scalar operations are applied to only the first value. For example, in
pseudo-code, a packed add would be executed as:
```
v1[0] = v1[0] + v2[0]
v1[1] = v1[1] + v2[1]
v1[2] = v1[2] + v2[2]
v1[3] = v1[3] + v2[3]
```
While a scalar add would only be:
```
v1[0] = v1[0] + v2[0]
```
The second letter refers to the data size: either Single or Double. This simply tells the
processor whether to use the register as four 32-bit floats or two 64-bit doubles, respectively.
## MMX Extensions
MMX extensions are designed to accelerate multimedia and communication applications by providing
instructions that can process multiple data elements in parallel.
## Usage
To use these extensions in your project, ensure that your compiler supports them and that you have
enabled the appropriate flags.
On Linux, you can run the command `lscpu`
Note: the examples here will compile, but not run on CPU architectures != amd64, like ARM or RISCV .
@@ -0,0 +1,39 @@
// MMX Instruction Set
// Several suffixes are used to indicate what data size the instruction operates on:
// Byte (8 bits)
// Word (16 bits)
// Double word (32 bits)
// Quad word (64 bits)
// The signedness of the operation is also signified by the suffix: US for unsigned and S for signed.
// For example, PSUBUSB subtracts unsigned bytes, while PSUBSD subtracts signed double words.
// MMX defined over 40 new instructions, listed below.
// EMMS, MOVD, MOVQ, PACKSSDW, PACKSSWB, PACKUSWB, PADDB, PADDD, PADDSB, PADDSW, PADDUSB, PADDUSW,
// PADDW, PAND, PANDN, PCMPEQB, PCMPEQD, PCMPEQW, PCMPGTB, PCMPGTD, PCMPGTW, PMADDWD, PMULHW, PMULLW,
// POR, PSLLD, PSLLQ, PSLLW, PSRAD, PSRAW, PSRLD, PSRLQ, PSRLW, PSUBB, PSUBD, PSUBSB, PSUBSW, PSUBUSB,
// PSUBUSW, PSUBW, PUNPCKHBW, PUNPCKHDQ, PUNPCKHWD, PUNPCKLBW, PUNPCKLDQ, PUNPCKLWD, PXOR
@[if amd64 && !tinyc && !msvc]
fn add_vectors_mmx(a &u8, b &u8, result &u8) {
unsafe {
asm volatile amd64 {
movq mm0, [a] // Load 8 bytes from a into MMX register mm0
movq mm1, [b] // Load 8 bytes from b into MMX register mm1
paddb mm0, mm1 // Add the two vectors using MMX instruction
movq [result], mm0 // Store the result back to memory
; ; r (a)
r (b)
r (result)
; mm0
mm1
}
}
}
fn main() {
a := [u8(1), 2, 3, 4, 5, 6, 7, 8]
b := [u8(8), 7, 6, 5, 4, 3, 2, 1]
result := []u8{len: 8}
add_vectors_mmx(&a[0], &b[0], &result[0])
println(result)
assert result == [u8(9), 9, 9, 9, 9, 9, 9, 9]
}
@@ -0,0 +1,37 @@
// SSE Instruction Set
// SSE: Added with Pentium III
// Floating-point Instructions:
// ADDPS, ADDSS, CMPPS, CMPSS, COMISS, CVTPI2PS, CVTPS2PI, CVTSI2SS, CVTSS2SI, CVTTPS2PI, CVTTSS2SI,
// DIVPS, DIVSS, LDMXCSR, MAXPS, MAXSS, MINPS, MINSS, MOVAPS, MOVHLPS, MOVHPS, MOVLHPS, MOVLPS,
// MOVMSKPS, MOVNTPS, MOVSS, MOVUPS, MULPS, MULSS, RCPPS, RCPSS, RSQRTPS, RSQRTSS, SHUFPS, SQRTPS,
// SQRTSS, STMXCSR, SUBPS, SUBSS, UCOMISS, UNPCKHPS, UNPCKLPS
//
// Integer Instructions:
// ANDNPS, ANDPS, ORPS, PAVGB, PAVGW, PEXTRW, PINSRW, PMAXSW, PMAXUB, PMINSW, PMINUB, PMOVMSKB, PMULHUW, PSADBW, PSHUFW, XORPS
// The ADDPS instruction adds two vectors of floats using SSE instructions.
@[if amd64 && !tinyc && !msvc]
fn add_vectors_sse(a &f32, b &f32, result &f32) {
unsafe {
asm volatile amd64 {
movups xmm0, [a] // Load 4 floats from array a into SSE register xmm0
movups xmm1, [b] // Load 4 floats from array b into SSE register xmm1
addps xmm0, xmm1 // Add the two vectors using SSE instruction
movups [result], xmm0 // Store the result back to memory
; ; r (a)
r (b)
r (result)
; xmm0
xmm1
}
}
}
fn main() {
a := [f32(1.0), 2.0, 3.0, 4.0]
b := [f32(4.0), 3.0, 2.0, 1.0]
result := []f32{len: 4}
add_vectors_sse(&a[0], &b[0], &result[0])
println(result)
assert result == [f32(5.0), 5.0, 5.0, 5.0]
}
@@ -0,0 +1,42 @@
// SSE Instruction Set
// SSE2: Added with Pentium 4
// Floating-point Instructions:
// ADDPD, ADDSD, ANDNPD, ANDPD, CMPPD, CMPSD*, COMISD, CVTDQ2PD, CVTDQ2PS, CVTPD2DQ, CVTPD2PI,
// CVTPD2PS, CVTPI2PD, CVTPS2DQ, CVTPS2PD, CVTSD2SI, CVTSD2SS, CVTSI2SD, CVTSS2SD, CVTTPD2DQ,
// CVTTPD2PI, CVTTPS2DQ, CVTTSD2SI, DIVPD, DIVSD, MAXPD, MAXSD, MINPD, MINSD, MOVAPD, MOVHPD,
// MOVLPD, MOVMSKPD, MOVSD*, MOVUPD, MULPD, MULSD, ORPD, SHUFPD, SQRTPD, SQRTSD, SUBPD, SUBSD,
// UCOMISD, UNPCKHPD, UNPCKLPD, XORPD
// * CMPSD and MOVSD have the same name as the string instruction mnemonics CMPSD (CMPS) and
// MOVSD (MOVS); however, the former refer to scalar double-precision floating-points whereas
// the latter refer to doubleword strings.
// Integer Instructions:
// MOVDQ2Q, MOVDQA, MOVDQU, MOVQ2DQ, PADDQ, PSUBQ, PMULUDQ, PSHUFHW, PSHUFLW, PSHUFD, PSLLDQ, PSRLDQ, PUNPCKHQDQ, PUNPCKLQDQ
// The MULPD instruction multiplies two vectors of doubles using SSE2 instructions.
@[if amd64 && !tinyc && !msvc]
fn multiply_vectors_sse2(a &f64, b &f64, result &f64) {
unsafe {
asm volatile amd64 {
movupd xmm0, [a] // Load 2 doubles from array a into SSE2 register xmm0
movupd xmm1, [b] // Load 2 doubles from array b into SSE2 register xmm1
mulpd xmm0, xmm1 // Multiply the two vectors using SSE2 instruction
movupd [result], xmm0 // Store the result back to memory
; ; r (a)
r (b)
r (result)
; xmm0
xmm1
}
}
}
fn main() {
a := [f64(1.5), 2.5]
b := [f64(3.5), 4.5]
result := []f64{len: 2}
multiply_vectors_sse2(&a[0], &b[0], &result[0])
println(result)
// 5.25 = 1.5 * 3.5
// 11.25 = 2.5 * 4.5
assert result == [f64(5.25), 11.25]
}
@@ -0,0 +1,36 @@
// SSE Instruction Set
// SSE3: Added with later Pentium 4
// ADDSUBPD, ADDSUBPS, HADDPD, HADDPS, HSUBPD, HSUBPS, MOVDDUP, MOVSHDUP, MOVSLDUP
// The HADDPS instruction performs horizontal addition of two vectors of floats using SSE3
// instructions.
@[if amd64 && !tinyc && !msvc]
fn horizontal_add_sse3(a &f32, b &f32, result &f32) {
unsafe {
asm volatile amd64 {
movaps xmm0, [a] // Load 4 floats from array a into SSE3 register xmm0
movaps xmm1, [b] // Load 4 floats from array b into SSE3 register xmm1
haddps xmm0, xmm1 // Perform horizontal add of xmm0 and xmm1
movaps [result], xmm0 // Store the result back to memory
; ; r (a)
r (b)
r (result)
; xmm0
xmm1
}
}
}
fn main() {
a := [f32(1.0), 2.0, 3.0, 4.0]
b := [f32(5.0), 6.0, 7.0, 8.0]
result := []f32{len: 4}
horizontal_add_sse3(&a[0], &b[0], &result[0])
println(result)
// The result should be [3.0, 7.0, 11.0, 15.0] due to horizontal addition.
// 1.0 + 2.0 = 3.0
// 3.0 + 4.0 = 7.0
// 5.0 + 6.0 = 11.0
// 7.0 + 8.0 = 15.0
assert result == [f32(3.0), 7.0, 11.0, 15.0]
}
@@ -0,0 +1,31 @@
// SSE Instruction Set
// SSE4.1: Added with later Core 2
// MPSADBW, PHMINPOSUW, PMULLD, PMULDQ, DPPS, DPPD, BLENDPS, BLENDPD, BLENDVPS, BLENDVPD,
// PBLENDVB, PBLENDW, PMINSB, PMAXSB, PMINUW, PMAXUW, PMINUD, PMAXUD, PMINSD, PMAXSD, ROUNDPS,
// ROUNDSS, ROUNDPD, ROUNDSD, INSERTPS, PINSRB, PINSRD, PINSRQ, EXTRACTPS, PEXTRB, PEXTRW,
// PEXTRD, PEXTRQ, PMOVSXBW, PMOVZXBW, PMOVSXBD, PMOVZXBD, PMOVSXBQ, PMOVZXBQ, PMOVSXWD,
// PMOVZXWD, PMOVSXWQ, PMOVZXWQ, PMOVSXDQ, PMOVZXDQ, PTEST, PCMPEQQ, PACKUSDW, MOVNTDQA
@[if amd64 && !tinyc && !msvc]
fn round_floats_sse4_1(a &f32, result &f32) {
unsafe {
asm volatile amd64 {
movups xmm0, [a] // Load 4 floats from array a into xmm0
roundps xmm0, xmm0, 0 // Round to nearest integer
movups [result], xmm0 // Store the result in result array
; ; r (a)
r (result)
; xmm0
}
}
}
fn main() {
a := [f32(1.2), 2.5, 3.8, 4.4]
result := []f32{len: 4}
// Rounding mode 0 corresponds to rounding to the nearest integer
round_floats_sse4_1(&a[0], &result[0])
println(result)
// The expected rounded result should be [1.0, 2.0, 4.0, 4.0]
assert result == [f32(1.0), 2.0, 4.0, 4.0]
}
@@ -0,0 +1,31 @@
// SSE Instruction Set
// SSSE3: Added with Xeon 5100 and early Core 2
// PSIGNW, PSIGND, PSIGNB, PSHUFB, PMULHRSW, PMADDUBSW, PHSUBW, PHSUBSW, PHSUBD, PHADDW, PHADDSW,
// PHADDD, PALIGNR, PABSW, PABSD, PABSB
// The PSIGNW instruction negates or leaves elements unchanged based on another vector's signs.
@[if amd64 && !tinyc && !msvc]
fn psignw_example(a &i16, b &i16, result &i16) {
unsafe {
asm volatile amd64 {
movdqa xmm0, [a] // Load 8 signed 16-bit integers from array a into xmm0
movdqa xmm1, [b] // Load 8 signed 16-bit integers from array b into xmm1
psignw xmm0, xmm1 // Adjust the sign of elements in xmm0 based on xmm1
movdqa [result], xmm0 // Store the result back to memory
; ; r (a)
r (b)
r (result)
; xmm0
xmm1
}
}
}
fn main() {
a0 := [i16(1), -2, 3, -4, 5, -6, 7, -8]
b0 := [i16(1), -1, 1, -1, 1, -1, 1, -1]
result0 := []i16{len: 8}
psignw_example(&a0[0], &b0[0], &result0[0])
dump(result0)
assert result0 == [i16(1), 2, 3, 4, 5, 6, 7, 8]
}
+20
View File
@@ -0,0 +1,20 @@
import semver
fn main() {
semver.from('asd') or { check_error(err) }
semver.from('') or { check_error(err) }
}
fn check_error(err IError) {
match err {
semver.InvalidVersionFormatError {
println('wrong format')
}
semver.EmptyInputError {
println('empty input')
}
else {
println('unknown error')
}
}
}
+17
View File
@@ -0,0 +1,17 @@
// vtest build: !(macos || windows)
import db.mysql
fn main() {
mut conn := mysql.connect(
host: 'localhost'
port: 3306
username: 'root'
password: ''
dbname: 'mysql'
)!
res := conn.query('show tables')!
for row in res.rows() {
println(row.vals.join(', '))
}
conn.close()!
}
+161
View File
@@ -0,0 +1,161 @@
// vtest build: !(macos || windows)
import db.mysql
import pool
import time
// Define your connection factory function
fn create_conn() !&pool.ConnectionPoolable {
config := mysql.Config{
host: '127.0.0.1'
port: 3306
username: 'root'
password: '12345678'
dbname: 'mysql'
}
db := mysql.connect(config)!
return &db
}
fn main() {
// Configure pool parameters
config := pool.ConnectionPoolConfig{
max_conns: 50
min_idle_conns: 5
max_lifetime: 2 * time.hour
idle_timeout: 30 * time.minute
get_timeout: 5 * time.second
}
// Create connection pool
mut my_pool := pool.new_connection_pool(create_conn, config)!
defer {
// When application exits
my_pool.close()
}
// Acquire connection
mut conn := my_pool.get()!
defer {
// Return connection to pool
my_pool.put(conn) or { println(err) }
}
// Convert `conn` to a `mysql.DB` object
mut db := conn as mysql.DB
assert db.validate()!
mut response := db.exec('drop table if exists users')!
assert response == []mysql.Row{}
response = db.exec('create table if not exists users (
id INT PRIMARY KEY AUTO_INCREMENT,
username TEXT,
last_name TEXT NULL DEFAULT NULL
)')!
assert response == []mysql.Row{}
mut result_code := db.exec_none('insert into users (username) values ("jackson")')
assert result_code == 0
result_code = db.exec_none('insert into users (username) values ("shannon")')
assert result_code == 0
result_code = db.exec_none('insert into users (username) values ("bailey")')
assert result_code == 0
result_code = db.exec_none('insert into users (username) values ("blaze")')
assert result_code == 0
rows := db.exec_param('insert into users (username) values (?)', 'Hi')!
assert rows == []mysql.Row{}
// Regression testing to ensure the query and exec return the same values
res := db.query('select * from users')!
response = res.rows()
assert response[0].vals[1] == 'jackson'
response = db.exec('select * from users')!
assert response[0].vals[1] == 'jackson'
response = db.exec('select * from users where id = 400')!
assert response.len == 0
single_row := db.exec_one('select * from users')!
assert single_row.vals[1] == 'jackson'
response = db.exec_param_many('select * from users where username = ?', [
'jackson',
])!
assert response[0] == mysql.Row{
vals: ['1', 'jackson', '']
}
response = db.exec_param_many('select * from users where username = ? and id = ?', [
'bailey',
'3',
])!
assert response[0] == mysql.Row{
vals: ['3', 'bailey', '']
}
response = db.exec_param_many('select * from users', [''])!
assert response == [
mysql.Row{
vals: ['1', 'jackson', '']
},
mysql.Row{
vals: ['2', 'shannon', '']
},
mysql.Row{
vals: ['3', 'bailey', '']
},
mysql.Row{
vals: ['4', 'blaze', '']
},
mysql.Row{
vals: ['5', 'Hi', '']
},
]
response = db.exec_param('select * from users where username = ?', 'blaze')!
assert response[0] == mysql.Row{
vals: ['4', 'blaze', '']
}
// transaction test
// turn off `autocommit` first
db.autocommit(false)!
// begin a new transaction
db.begin()!
result_code = db.exec_none('insert into users (username) values ("tom")')
assert result_code == 0
// make a savepoint
db.savepoint('savepoint1')!
result_code = db.exec_none('insert into users (username) values ("kitty")')
assert result_code == 0
// rollback to `savepoint1`
db.rollback_to('savepoint1')!
result_code = db.exec_none('insert into users (username) values ("mars")')
assert result_code == 0
db.commit()!
response = db.exec_param_many('select * from users', [''])!
assert response == [
mysql.Row{
vals: ['1', 'jackson', '']
},
mysql.Row{
vals: ['2', 'shannon', '']
},
mysql.Row{
vals: ['3', 'bailey', '']
},
mysql.Row{
vals: ['4', 'blaze', '']
},
mysql.Row{
vals: ['5', 'Hi', '']
},
mysql.Row{
vals: ['6', 'tom', '']
},
mysql.Row{
vals: ['8', 'mars', '']
},
]
}
+293
View File
@@ -0,0 +1,293 @@
// vtest build: !(macos || windows)
import os
import db.sqlite
import db.mysql
import db.pg
// The goal of this example, is to show how you can connect to
// several different databases in the same program, and use both
// the ORM and the native connection wrapper, that each DB driver
// provides, if you need to execute more complex SQL queries.
//
// You can use environment variables to pass your local DB connection
// settings, without editing the code, like this:
//
// MUSER='myuser' MPASS='abc' MDATABASE='vtestdb' PGUSER='postgres' PGPASS='password' PGDATABASE='postgres' ./v -g run examples/database/orm.v
//
// WARNING: this example will drop and re-create any tables named:
// * modules
// * User
// * Parent
// * Child
// in the passed databases, so it is better to use empty DBs for it.
const mysql_host = os.getenv_opt('MHOST') or { 'localhost' }
const mysql_port = os.getenv_opt('MPORT') or { '3306' }.u32()
const mysql_user = os.getenv_opt('MUSER') or { 'myuser' }
const mysql_pass = os.getenv_opt('MPASS') or { 'abc' }
const mysql_db = os.getenv_opt('MDATABASE') or { 'test' }
const pg_host = os.getenv_opt('PGHOST') or { 'localhost' }
const pg_user = os.getenv_opt('PGUSER') or { 'test' }
const pg_pass = os.getenv_opt('PGPASS') or { 'abc' }
const pg_db = os.getenv_opt('PGDATABASE') or { 'test' }
@[table: 'modules']
struct Module {
id int @[primary; sql: serial]
name string
nr_downloads int @[sql: u64]
creator User
}
struct User {
id int @[primary; sql: serial]
age u32 @[unique: 'user']
name string @[sql: 'username'; sql_type: 'VARCHAR(200)'; unique]
is_customer bool @[sql: 'abc'; unique: 'user']
skipped_string string @[skip]
}
struct Parent {
id int @[primary; sql: serial]
name string
children []Child @[fkey: 'parent_id']
}
struct Child {
id int @[primary; sql: serial]
parent_id int
name string
}
fn sqlite3_array() ! {
eprintln('------------ ${@METHOD} -----------------')
mut db := sqlite.connect(':memory:')!
defer {
sql db {
drop table Parent
drop table Child
} or {}
db.close() or {}
}
sql db {
create table Parent
}!
sql db {
create table Child
}!
par := Parent{
name: 'test'
children: [
Child{
name: 'abc'
},
Child{
name: 'def'
},
]
}
sql db {
insert par into Parent
}!
parent := sql db {
select from Parent where id == 1
}!
eprintln(parent)
}
fn msql_array() ! {
eprintln('------------ ${@METHOD} -----------------')
mut db := mysql.connect(
host: mysql_host
port: mysql_port
username: mysql_user
password: mysql_pass
dbname: mysql_db
)!
defer {
sql db {
drop table Parent
} or {}
db.close() or {}
}
db.query('drop table if exists Parent')!
db.query('drop table if exists Child')!
sql db {
create table Parent
create table Child
}!
par := Parent{
name: 'test'
children: [
Child{
name: 'abc'
},
Child{
name: 'def'
},
]
}
sql db {
insert par into Parent
}!
parent := sql db {
select from Parent where id == 1
}!
eprintln(parent)
}
fn psql_array() ! {
eprintln('------------ ${@METHOD} -----------------')
mut db := pg.connect(host: pg_host, user: pg_user, password: pg_pass, dbname: pg_db)!
defer {
db.exec_one('drop table if exists "Parent", "Child"') or { eprintln(err) }
db.close() or {}
}
db.exec_one('drop table if exists "Parent", "Child"') or { eprintln(err) }
sql db {
create table Parent
create table Child
}!
par := Parent{
name: 'test'
children: [
Child{
name: 'abc'
},
Child{
name: 'def'
},
]
}
sql db {
insert par into Parent
}!
parent := sql db {
select from Parent where id == 1
}!
eprintln(parent)
}
fn sqlite3() ! {
eprintln('------------ ${@METHOD} -----------------')
mut db := sqlite.connect(':memory:')!
defer {
sql db {
drop table Module
drop table User
} or {}
db.close() or {}
}
sql db {
create table Module
}!
sql db {
create table User
}!
mod := Module{
name: 'test'
nr_downloads: 10
creator: User{
age: 21
name: 'VUser'
is_customer: true
}
}
sql db {
insert mod into Module
}!
modul := sql db {
select from Module where id == 1
}!
eprintln(modul)
}
fn msql() ! {
eprintln('------------ ${@METHOD} -----------------')
mut conn := mysql.connect(
host: mysql_host
port: mysql_port
username: mysql_user
password: mysql_pass
dbname: mysql_db
)!
defer {
conn.query('DROP TABLE IF EXISTS Module') or { eprintln(err) }
conn.query('DROP TABLE IF EXISTS User') or { eprintln(err) }
conn.close() or {}
}
conn.query('DROP TABLE IF EXISTS Module') or { eprintln(err) }
conn.query('DROP TABLE IF EXISTS User') or { eprintln(err) }
sql conn {
create table Module
}!
sql conn {
create table User
}!
mod := Module{
name: 'test'
nr_downloads: 10
creator: User{
age: 21
name: 'VUser'
is_customer: true
}
}
sql conn {
insert mod into Module
}!
m := sql conn {
select from Module where id == 1
}!
eprintln(m)
}
fn psql() ! {
eprintln('------------ ${@METHOD} -----------------')
mut db := pg.connect(host: pg_host, user: pg_user, password: pg_pass, dbname: pg_db)!
defer {
db.exec_one('drop table if exists "modules", "User"') or { eprintln(err) }
db.close() or {}
}
db.exec_one('drop table if exists "modules", "User"') or { eprintln(err) }
sql db {
create table Module
create table User
}!
mod := Module{
name: 'test'
nr_downloads: 10
creator: User{
age: 21
name: 'VUser'
is_customer: true
}
}
sql db {
insert mod into Module
}!
modul := sql db {
select from Module where id == 1
}!
sql db {
drop table Module
}!
eprintln(modul)
}
fn main() {
eprintln('------------ ${@METHOD} -----------------')
sqlite3_array()!
msql_array()!
psql_array()!
sqlite3()!
msql()!
psql()!
}
@@ -0,0 +1 @@
customer
+71
View File
@@ -0,0 +1,71 @@
// vtest build: linux
module main
import db.pg
struct Customer {
id int
name string
nr_orders int
country string
}
const dash = '----------------------------------------------------------------'
fn main() {
db := pg.connect(
host: 'localhost' // or '127.0.0.1'
user: 'postgres'
dbname: 'customerdb'
) or {
eprintln('failed to connect, error: ${err}')
return
}
nr_customers := sql db {
select count from Customer
}!
println('Total customers: ${nr_customers}')
println(dash)
bg_country := 'Bulgaria'
// V syntax can be used to build queries
bg_customers := sql db {
select from Customer where country == bg_country && id != 2
}!
for customer in bg_customers {
println('${customer.country} | ${customer.id} - ${customer.name}')
}
println(dash)
ru_customers := sql db {
select from Customer where country == 'Russia'
}!
for customer in ru_customers {
println('${customer.country} | ${customer.id} - ${customer.name}')
}
// by adding `limit 1` we tell V that there will be only one object
println(dash)
existing := sql db {
select from Customer where id == 1 limit 1
}!
println('Existing customer name: ${existing}[0].name')
println('Existing customer full information:')
println(existing)
println(dash)
q := Customer{}
// It's easy to handle queries that don't return any data
_ := sql db {
select from Customer where id == 12345 && name == q.name && nr_orders > q.nr_orders limit 1
}!
// Insert a new customer
nc := Customer{
name: 'John Doe'
nr_orders: 10
}
sql db {
insert nc into Customer
}!
}
+122
View File
@@ -0,0 +1,122 @@
--
-- PostgreSQL database dump
--
-- Dumped from database version 9.5.19
-- Dumped by pg_dump version 9.5.19
SET statement_timeout = 0;
SET lock_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
--
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
--
-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
--
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: customers; Type: TABLE; Schema: public; Owner: myuser
--
CREATE TABLE public.customers (
id integer NOT NULL,
name text DEFAULT ''::text,
nr_orders integer DEFAULT 0,
country text DEFAULT 'England'::text,
created_at timestamp without time zone DEFAULT now()
);
ALTER TABLE public.customers OWNER TO myuser;
--
-- Name: customers_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser
--
CREATE SEQUENCE public.customers_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.customers_id_seq OWNER TO myuser;
--
-- Name: customers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser
--
ALTER SEQUENCE public.customers_id_seq OWNED BY public.customers.id;
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: myuser
--
ALTER TABLE ONLY public.customers ALTER COLUMN id SET DEFAULT nextval('public.customers_id_seq'::regclass);
--
-- Data for Name: customers; Type: TABLE DATA; Schema: public; Owner: myuser
--
COPY public.customers (id, name, nr_orders, country, created_at) FROM stdin;
2 Pippi Långstrump 3 Bulgaria 2019-08-19 09:41:30.78888
1 Bilbo Begins 11 Bulgaria 2019-08-19 09:40:31.396807
3 Viktualia Rullgardina 0 Bulgaria 2019-08-19 09:42:52.723223
4 Krusmynta Efraimsdotter 5 Bulgaria 2019-08-19 09:43:04.083209
5 Ana Karenina 0 Russia 2019-08-20 15:41:50.244971
7 Jiji Lolobridgida 0 Italy 2019-08-20 15:42:26.020113
6 Viktor Savashkin 8 Russia 2019-08-20 15:42:07.213557
\.
--
-- Name: customers_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser
--
SELECT pg_catalog.setval('public.customers_id_seq', 1, true);
--
-- Name: customers_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser
--
ALTER TABLE ONLY public.customers
ADD CONSTRAINT customers_pkey PRIMARY KEY (id);
--
-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres
--
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;
--
-- PostgreSQL database dump complete
--
+21
View File
@@ -0,0 +1,21 @@
import db.sqlite
fn main() {
db := sqlite.connect(':memory:')!
db.exec("create table users (id integer primary key, name text default '');") or { panic(err) }
db.exec("insert into users (name) values ('Sam')")!
db.exec("insert into users (name) values ('Peter')")!
db.exec("insert into users (name) values ('Kate')")!
nr_users := db.q_int('select count(*) from users')!
println('nr users = ${nr_users}')
name := db.q_string('select name from users where id = 1')!
assert name == 'Sam'
users := db.exec('select * from users')!
for row in users {
println(row.vals)
}
}
+41
View File
@@ -0,0 +1,41 @@
module main
import arrays.diff
import os
// diff_files generate diff for two files.
fn diff_files(src_file string, dst_file string) !string {
src := os.read_lines(src_file)!
dst := os.read_lines(dst_file)!
mut ctx := diff.diff(src, dst)
return ctx.generate_patch(colorful: true, block_header: true)
}
fn main() {
f1 := "Module{
name: 'Foo'
description: 'Awesome V module.'
version: '0.0.0'
dependencies: []
}
"
f2 := "Module{
name: 'foo'
description: 'Awesome V module.'
version: '0.1.0'
license: 'MIT'
dependencies: []
}
"
p1 := 'diff_f1.txt'
p2 := 'diff_f2.txt'
os.write_file(p1, f1)!
os.write_file(p2, f2)!
str := diff_files(p1, p2)!
println(str)
os.rm(p1)!
os.rm(p2)!
}
+10
View File
@@ -0,0 +1,10 @@
fn factorial(n u32) u32 {
if dump(n <= 1) {
return dump(1)
}
return dump(n * factorial(n - 1))
}
fn main() {
println(factorial(5))
}
@@ -0,0 +1,21 @@
module library
// add_1 is exported with the C name `add_1`.
// It can be called by external programs, when the module is compiled
// as a shared library.
// It is exported, because the function is declared as public with `pub`.
// The exported C name is `add_1`, because of the export: tag.
// (Normally, the exported name is a V mangled version based on the module
// name followed by __, followed by the fn name, i.e. it would have been
// `library__add_1`, if not for the export: tag).
@[export: 'add_1']
pub fn add_1(x int, y int) int {
num := my_private_function(x + y)
println('hello from ${@FN} , num = ${num}')
return num
}
// this function is not exported and will not be visible to external programs.
fn my_private_function(x int) int {
return 1 + x
}
@@ -0,0 +1,33 @@
module main
// Note: This program, requires that the shared library was already compiled.
// To do so, run `v -d no_backtrace -o library -shared modules/library/library.v`
// before running this program.
import os
import dl
import dl.loader
type FNAdder = fn (int, int) int
const cfolder = os.dir(@FILE)
const default_paths = [
os.join_path(cfolder, 'library${dl.dl_ext}'),
os.join_path(cfolder, 'location1/library${dl.dl_ext}'),
os.join_path(cfolder, 'location2/library${dl.dl_ext}'),
os.join_path(cfolder, 'modules/library/library${dl.dl_ext}'),
]
fn main() {
mut dl_loader := loader.get_or_create_dynamic_lib_loader(
key: cfolder + '/library'
paths: default_paths
)!
defer {
dl_loader.unregister()
}
sym := dl_loader.get_sym('add_1')!
f := FNAdder(sym)
eprintln('f: ${ptr_str(f)}')
res := f(1, 2)
eprintln('res: ${res}')
}
@@ -0,0 +1,67 @@
module main
import os
import dl
const vexe = os.real_path(os.getenv('VEXE'))
const so_ext = dl.dl_ext
fn test_vexe() {
// dump(vexe)
assert vexe != ''
// dump(os.executable())
// dump(@FILE)
// dump(cfolder)
// dump(so_ext)
}
fn test_can_compile_library() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, dl.get_libname('library'))
os.rm(library_file_path) or {}
v_compile('-d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_path)
}
fn test_can_compile_main_program() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, dl.get_libname('library'))
assert os.is_file(library_file_path)
result := v_compile('run use_shared_library.v')
// dump(result)
assert result.output.contains('hello from add_1 , num = 4')
assert result.output.contains('res: 4')
os.rm(library_file_path) or {}
}
fn test_can_compile_and_use_library_with_skip_unused_home_dir() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, dl.get_libname('library'))
os.rm(library_file_path) or {}
v_compile('-skip-unused -d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_path)
result := v_compile('run use_shared_library.v')
assert result.output.contains('res: 4')
os.rm(library_file_path) or {}
}
fn test_can_compile_and_use_library_with_skip_unused_location1_dir() {
os.chdir(cfolder) or {}
library_file_path := os.join_path(cfolder, 'location1', dl.get_libname('library'))
os.rm(library_file_path) or {}
os.mkdir('location1') or {}
v_compile('-skip-unused -d no_backtrace -o location1/library -shared modules/library/library.v')
assert os.is_file(library_file_path)
result := v_compile('run use_shared_library.v')
assert result.output.contains('res: 4')
os.rm(library_file_path) or {}
}
fn v_compile(vopts string) os.Result {
cmd := '${os.quoted_path(vexe)} -showcc ${vopts}'
// dump(cmd)
res := os.execute_or_exit(cmd)
// dump(res)
assert res.exit_code == 0
return res
}
@@ -0,0 +1,19 @@
module library
// add_1 is exported with the C name `add_1`.
// It can be called by external programs, when the module is compiled
// as a shared library.
// It is exported, because the function is declared as public with `pub`.
// The exported C name is `add_1`, because of the export: tag.
// (Normally, the exported name is a V mangled version based on the module
// name followed by __, followed by the fn name, i.e. it would have been
// `library__add_1`, if not for the export: tag).
@[export: 'add_1']
pub fn add_1(x int, y int) int {
return my_private_function(x + y)
}
// this function is not exported and will not be visible to external programs.
fn my_private_function(x int) int {
return 1 + x
}
@@ -0,0 +1,19 @@
module main
// Note: This program, requires that the shared library was already compiled.
// To do so, run `v -d no_backtrace -o library -shared modules/library/library.v`
// before running this program.
import os
import dl
type FNAdder = fn (int, int) int
fn main() {
library_file_path := os.join_path(os.dir(@FILE), dl.get_libname('library'))
handle := dl.open_opt(library_file_path, dl.rtld_lazy)!
eprintln('handle: ${ptr_str(handle)}')
f := FNAdder(dl.sym_opt(handle, 'add_1')!)
eprintln('f: ${ptr_str(f)}')
res := f(1, 2)
eprintln('res: ${res}')
}
@@ -0,0 +1,65 @@
module main
// vtest retry: 3
import os
import dl
const vexe = os.real_path(os.getenv('VEXE'))
const cfolder = os.dir(@FILE)
const so_ext = dl.dl_ext
const library_file_name = os.join_path(cfolder, dl.get_libname('library'))
fn test_vexe() {
// dump(vexe)
assert vexe != ''
// dump(os.executable())
// dump(@FILE)
// dump(cfolder)
// dump(so_ext)
// dump(library_file_name)
}
fn test_can_compile_library() {
os.chdir(cfolder) or {}
os.rm(library_file_name) or {}
v_compile('-d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_name)
}
fn test_can_compile_main_program() {
os.chdir(cfolder) or {}
assert os.is_file(library_file_name)
result := v_compile('run use_dl_module.v')
// dump(result)
assert result.output.contains('res: 4')
os.rm(library_file_name) or {}
}
fn test_can_compile_and_use_library_with_skip_unused() {
os.chdir(cfolder) or {}
os.rm(library_file_name) or {}
v_compile('-skip-unused -d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_name)
result := v_compile('run use_dl_module.v')
assert result.output.contains('res: 4')
os.rm(library_file_name) or {}
}
fn test_can_compile_and_use_library_with_prod() {
os.chdir(cfolder) or {}
os.rm(library_file_name) or {}
v_compile('-prod -d no_backtrace -o library -shared modules/library/library.v')
assert os.is_file(library_file_name)
result := v_compile('run use_dl_module.v')
assert result.output.contains('res: 4')
os.rm(library_file_name) or {}
}
fn v_compile(vopts string) os.Result {
cmd := '${os.quoted_path(vexe)} -showcc ${vopts}'
// dump(cmd)
res := os.execute_or_exit(cmd)
// dump(res)
assert res.exit_code == 0
return res
}
+20
View File
@@ -0,0 +1,20 @@
import semver
fn main() {
semver.from('asd') or { check_error(err) }
semver.from('') or { check_error(err) }
}
fn check_error(err IError) {
match err {
semver.InvalidVersionFormatError {
println('wrong format')
}
semver.EmptyInputError {
println('empty input')
}
else {
println('unknown error')
}
}
}
+18
View File
@@ -0,0 +1,18 @@
// This example shows 2 solutions for https://projecteuler.net/problem=1 :
fn gauss_sum(n int) fn (int) int {
return fn [n] (m int) int {
return m * ((n - 1) / m) * ((n - 1) / m + 1) / 2
}
}
gs := gauss_sum(1000)
println('O(1) arithmetic progression sum: ${gs(3) + gs(5) - gs(15)}')
// A brute force solution, by checking every n in the range:
mut sum := 0
for n in 1 .. 1000 {
if n % 3 == 0 || n % 5 == 0 {
sum += n
}
}
println('O(n) brute force calculated sum: ${sum}')
+33
View File
@@ -0,0 +1,33 @@
module main
import some_module
struct Receiver {
mut:
ok bool
}
fn main() {
mut sub := some_module.get_subscriber()
r := Receiver{}
sub.subscribe_method('event_foo', on_foo, r)
sub.subscribe('event_bar', on_bar)
sub.subscribe('event_baz', on_baz)
println('Receiver ok: ' + r.ok.str())
some_module.do_work()
println('Receiver ok: ' + r.ok.str())
}
fn on_foo(mut receiver Receiver, e &some_module.EventMetadata, _sender voidptr) {
receiver.ok = true
println('on_foo :: ' + e.message)
}
fn on_bar(_receiver voidptr, e &some_module.EventMetadata, _sender voidptr) {
println('on_bar :: ' + e.message)
}
fn on_baz(_receiver voidptr, _event voidptr, d &some_module.Duration) {
println('on_baz :: ' + d.hours.str())
}
@@ -0,0 +1,32 @@
module some_module
import eventbus
const eb = eventbus.new[string]()
pub struct Duration {
pub:
hours int
}
pub struct EventMetadata {
pub:
message string
}
pub fn do_work() {
duration := Duration{10}
for i in 0 .. 10 {
println('working...')
if i == 5 {
event_metadata := &EventMetadata{'Iteration ' + i.str()}
eb.publish('event_foo', duration, event_metadata)
eb.publish('event_bar', duration, event_metadata)
}
}
eb.publish('event_baz', &Duration{42}, &EventMetadata{'Additional data at the end.'})
}
pub fn get_subscriber() eventbus.Subscriber[string] {
return *eb.subscriber
}
+71
View File
@@ -0,0 +1,71 @@
# Fasthttp Example
A simple HTTP server example using the `fasthttp` module from `vlib/fasthttp`.
## Features
- Handles GET and POST requests
- Routes requests to different controllers based on HTTP method and path
- Returns appropriate HTTP responses with status codes and content
## Building
```sh
./v examples/fasthttp
```
## Running
```sh
./examples/fasthttp/fasthttp
```
The server will listen on `http://localhost:3000`
## Testing
### Home endpoint
```sh
curl http://localhost:3000/
```
### Get user by ID
```sh
curl http://localhost:3000/user/123
```
### Create user
```sh
curl -X POST http://localhost:3000/user
```
### 404 response
```sh
curl http://localhost:3000/notfound
```
## File Structure
- `main.v` - Entry point and request router
- `controllers.v` - Request handlers for different routes
- `v.mod` - Module metadata
## Architecture
The example demonstrates:
1. **Request Routing**: The `handle_request()` function routes incoming HTTP requests based on
method and path
2. **Response Handling**: Controllers return HTTP responses with proper headers and status codes
3. **Content Type**: All responses are returned as `[]u8` (byte arrays)
The fasthttp module handles:
- Low-level socket management
- Request parsing
- Connection handling
- Non-blocking I/O with epoll (Linux) or kqueue (macOS)
+27
View File
@@ -0,0 +1,27 @@
module main
fn home_controller() ![]u8 {
response := 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nHello, World!'
return response.bytes()
}
fn get_user_controller(id string) ![]u8 {
body := 'User ID: ${id}'
content_length := body.len
response := 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: ${content_length}\r\n\r\n${body}'
return response.bytes()
}
fn create_user_controller() ![]u8 {
body := 'User created successfully'
content_length := body.len
response := 'HTTP/1.1 201 Created\r\nContent-Type: text/plain\r\nContent-Length: ${content_length}\r\n\r\n${body}'
return response.bytes()
}
fn not_found_response() ![]u8 {
body := '404 Not Found'
content_length := body.len
response := 'HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: ${content_length}\r\n\r\n${body}'
return response.bytes()
}
+45
View File
@@ -0,0 +1,45 @@
module main
import fasthttp
fn handle_request(req fasthttp.HttpRequest) !fasthttp.HttpResponse {
method := req.buffer[req.method.start..req.method.start + req.method.len].bytestr()
path := req.buffer[req.path.start..req.path.start + req.path.len].bytestr()
if method == 'GET' {
if path == '/' {
return fasthttp.HttpResponse{
content: home_controller()!
}
} else if path.starts_with('/user/') {
id := path[6..]
return fasthttp.HttpResponse{
content: get_user_controller(id)!
}
}
} else if method == 'POST' {
if path == '/user' {
return fasthttp.HttpResponse{
content: create_user_controller()!
}
}
}
return fasthttp.HttpResponse{
content: not_found_response()!
}
}
fn main() {
mut server := fasthttp.new_server(fasthttp.ServerConfig{
port: 3000
handler: handle_request
}) or {
eprintln('Failed to create server: ${err}')
return
}
println('Starting fasthttp server on port http://localhost:3000...')
server.run() or { eprintln('error: ${err}') }
}
+6
View File
@@ -0,0 +1,6 @@
Module {
name: 'fasthttp_example'
description: 'A simple HTTP server example using the fasthttp module'
version: '0.0.1'
license: 'MIT'
}
+12
View File
@@ -0,0 +1,12 @@
import time
import net.http
fn main() {
resp := http.get('https://vlang.io/utc_now') or {
eprintln('Failed to fetch data from the server. Error: ${err}')
return
}
t := time.unix(resp.body.int())
println(t.format())
}
+41
View File
@@ -0,0 +1,41 @@
import json
import net.http
struct IpInfo {
ip string
ip_decimal int
country string
country_iso string
country_eu bool
region_name string
region_code string
zip_code string
city string
latitude f64
longitude f64
time_zone string
asn string
asn_org string
user_agent UserAgent
}
struct UserAgent {
product string
version string
comment string
raw_value string
}
url := 'http://ifconfig.co/json'
resp := http.fetch(
url: url
user_agent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0'
) or {
eprintln('failed to fetch data from the server, error: ${err}')
exit(1)
}
ip_info := json.decode(IpInfo, resp.body) or {
println('failed to decode the json data returned by the server, error: ${err}')
exit(2)
}
println(ip_info)
+31
View File
@@ -0,0 +1,31 @@
// This program displays the fibonacci sequence
const args = arguments()
fn main() {
// Check for user input
if args.len != 2 {
println('usage: fibonacci [rank]')
return
}
// Parse first argument and cast it to int
stop := args[1].int()
// Can only calculate correctly until rank 92
if stop > 92 {
println('rank must be 92 or less')
return
}
// Three consecutive terms of the sequence
mut a := i64(0)
mut b := i64(0)
mut c := i64(1)
println(a + b + c)
for _ in 0 .. stop {
// Set a and b to the next term
a = b
b = c
// Compute the new term
c = a + b
// Print the new term
println(c)
}
}
+18
View File
@@ -0,0 +1,18 @@
import os
fn main() {
files := os.ls('.') or {
println(err)
return
}
mut f := os.create('file_list.txt') or {
println(err)
return
}
for file in files {
if os.is_file(file) {
f.write_string(file + '\r\n') or { println(err) }
}
}
f.close()
}
+107
View File
@@ -0,0 +1,107 @@
import objects
import gg
import rand
struct App {
mut:
gg &gg.Context = unsafe { nil }
ui &objects.UIParams = unsafe { nil }
rockets []objects.Rocket
frames [][]objects.Rocket
// i thought about using a fixed fifo queue for the frames but the array
// seemed to work fine, if you'd like a challenge try implementing it with the queue :)
draw_flag bool = true
}
fn on_frame(mut app App) {
if !app.draw_flag {
return
}
app.gg.begin()
// drawing previous frames
for mut frame in app.frames {
for mut rocket in frame {
if !rocket.exploded {
rocket.color.a = u8(f32_max(rocket.color.a - 8, 0))
rocket.draw(mut app.gg)
}
}
}
// chance of firing new rocket
if rand.intn(30) or { 0 } == 0 {
app.rockets << objects.new_rocket()
}
// simulating rockets
app.rockets = app.rockets.filter(!it.dead)
for mut rocket in app.rockets {
rocket.tick(mut app.gg)
}
// adding frame
mut frame := app.rockets.clone()
for mut rocket in frame {
rocket.particles = []
}
app.frames << frame
// trimming out frames
if app.frames.len > 30 {
app.frames.delete(0)
}
app.gg.end()
}
fn on_event(e &gg.Event, mut app App) {
match e.typ {
.resized, .resumed {
app.resize()
}
.iconified {
app.draw_flag = false
}
.restored {
app.draw_flag = true
app.resize()
}
else {
// println("Type ${e.typ}")
}
}
}
fn (mut app App) resize() {
size := gg.window_size()
// avoid calls when minimized
if size.width < 2 && size.height < 2 {
return
}
mut s := gg.dpi_scale()
if s == 0.0 {
s = 1.0
}
app.ui.dpi_scale = s
app.ui.width = size.width
app.ui.height = size.height
}
fn main() {
mut app := &App{}
app.ui = objects.get_params()
app.gg = gg.new_context(
width: app.ui.width
height: app.ui.height
window_title: 'Fireworks!'
bg_color: gg.black
user_data: app
frame_fn: on_frame
event_fn: on_event
)
app.gg.run()
}
@@ -0,0 +1,12 @@
module objects
import gg
import rand
pub fn random_color() gg.Color {
return gg.Color{
r: rand.u8()
g: rand.u8()
b: rand.u8()
}
}
Loaded 100 of 11957 files, more files were not shown because too many files have changed in this diff. Show more