Compare commits

...

31 Commits

Author SHA1 Message Date
Jef Roosens e9aca12560
ci: switched to vieter-v
ci/woodpecker/push/vc Pipeline was successful Details
ci/woodpecker/push/docker Pipeline was successful Details
ci/woodpecker/push/arch Pipeline was successful Details
2022-06-24 08:47:37 +02:00
Jef Roosens ad966c29c2
ci: disable gc by default 2022-06-24 08:47:37 +02:00
Jef Roosens 0e3efb655e
ci: Added docker workflow & Dockerfile 2022-06-24 08:47:37 +02:00
Jef Roosens b817f8ebc9
ci: bumped alpine version & added mandoc to builder image 2022-06-24 08:47:36 +02:00
Jef Roosens 3d0ecd0a2d
ci: Added PKGBUILD & workflow for deploying Arch packages 2022-06-24 08:47:36 +02:00
Jef Roosens 77b5845c2c
ci: Added docker workflow & Dockerfile 2022-06-24 08:47:34 +02:00
CC d336b7b877
examples: add another vweb example, showing file upload, transform, and file download (#14842) 2022-06-24 09:23:47 +03:00
Delyan Angelov ccc3271493 docs: restore the important sentence about the mutable args that have to be marked on call 2022-06-23 21:15:21 +03:00
Emirhan Yener e5bbb23389
examples: fix 2048 gameplay (#14838) 2022-06-23 21:07:30 +03:00
Wertzui123 c10ba6d81a
os: add `.cmd` to the list of Windows executable suffixes (#14839) 2022-06-23 20:12:29 +03:00
Delyan Angelov b0fe21f018
ci: fix long lines/links in docs.md 2022-06-23 20:09:20 +03:00
Alexander Medvednikov c17200c33d
doc: clear up concurrency and immutable fn args 2022-06-23 17:46:57 +03:00
Larpon 298dc77c38
ci: add pure `-os android` checks (#14837) 2022-06-23 11:22:55 +03:00
yuyi e9a8f5fcc7
cgen: fix ref_struct.str() with null pointer (#14836) 2022-06-23 08:35:21 +03:00
Wertzui123 587101a1ea
os: fix `find_abs_path_of_executable` function (on Windows) (#14835) 2022-06-23 03:36:15 +03:00
Delyan Angelov 2524207d1c
tools: support c2v.exe in `v translate`, use os.quoted_path, cleanup errors. 2022-06-23 03:31:10 +03:00
Alexander Medvednikov 78c527b243
tutorials: improve c2v tutorial a bit 2022-06-23 02:45:14 +03:00
Alexander Medvednikov dbc51a4579
readme: add c2v demo 2022-06-23 02:33:09 +03:00
l-m ed8c63cc0b
builtin: add a contains_only method on string (#14830) 2022-06-23 01:41:42 +03:00
Delyan Angelov a7108ff05c
docs: use the actual most recent output of c2v in the example 2022-06-23 01:39:39 +03:00
Delyan Angelov b8d9bfec16
docs: fix C primes example in the tutorials (fixes c2v translation on linux) 2022-06-23 01:35:45 +03:00
Delyan Angelov 436081a9f5
ci: fix `v check-md tutorials/` 2022-06-23 01:30:22 +03:00
Delyan Angelov 1b87a4770c
tutorials: rename the folder containing spaces, to fix git checkouts on windows 2022-06-23 01:08:07 +03:00
Delyan Angelov 856270cac2
tutorials: restore the old tutorials/building_a_simple_web_blog_with_vweb/ folder. 2022-06-23 00:11:59 +03:00
Alexander Medvednikov 989c5e26f5 c2v: use https git clone 2022-06-22 22:49:29 +03:00
Alexander Medvednikov fe673e7963 tutorials: rename vweb tutorial 2022-06-22 22:48:40 +03:00
Alexander Medvednikov ae2183043b tutorials: c2v 2022-06-22 22:47:43 +03:00
Dialga 5cd5d551e3
v.pkgconfig: fix building standalone pkgconfig (#14825)
* Update main.v

* add test to prevent future regressions

Co-authored-by: Delyan Angelov <delian66@gmail.com>
2022-06-22 22:38:50 +03:00
Alexander Medvednikov c9ab086029 cmd/tools: enable c2v 2022-06-22 22:10:46 +03:00
yuyi e6c3de2f46
cgen: format cgen.init() generated c codes (#14824) 2022-06-22 21:39:15 +03:00
Larpon 3fb88500a2
os: improve Android interop (#14827) 2022-06-22 21:38:27 +03:00
34 changed files with 1061 additions and 79 deletions

View File

@ -0,0 +1,67 @@
#!/usr/bin/env -S v
module main
import os
import vab.vxt
import vab.android.ndk
fn main() {
assert ndk.found()
assert vxt.found()
work_dir := os.join_path(os.temp_dir(), 'android_cross_compile_test')
os.rm(work_dir) or {}
os.mkdir_all(work_dir) or { panic(err) }
vexe := vxt.vexe()
examples_dir := os.join_path(vxt.home(), 'examples')
v_example := os.join_path(examples_dir, 'toml.v')
ndk_version := ndk.default_version()
sysroot_path := ndk.sysroot_path(ndk_version) or { panic(err) }
include_path := os.join_path(sysroot_path, 'usr', 'include')
android_include_path := os.join_path(include_path, 'android')
//'-I"$include_path"'
cflags := ['-I"$android_include_path"', '-Wno-unused-value', '-Wno-implicit-function-declaration',
'-Wno-int-conversion']
for arch in ndk.supported_archs {
for level in ['min', 'max'] {
compiler_api := match level {
'min' {
ndk.compiler_min_api(.c, ndk_version, arch) or { panic(err) }
}
'max' {
ndk.compiler_max_api(.c, ndk_version, arch) or { panic(err) }
}
else {
panic('invalid min/max level')
}
}
os.setenv('VCROSS_COMPILER_NAME', compiler_api, true)
c_file := os.join_path(work_dir, arch + '-' + level + '.c')
o_file := os.join_path(work_dir, arch + '-' + level + '.o')
// x.v -> x.c
v_compile_cmd := '$vexe -o $c_file -os android -gc none $v_example'
vres := os.execute(v_compile_cmd)
if vres.exit_code != 0 {
panic('"$v_compile_cmd" failed: $vres.output')
}
assert os.exists(c_file)
// x.c -> x.o
compile_cmd := '$compiler_api ${cflags.join(' ')} -c $c_file -o $o_file'
cres := os.execute(compile_cmd)
if cres.exit_code != 0 {
panic('"$compile_cmd" failed: $cres.output')
}
assert os.exists(o_file)
compiler_exe_name := os.file_name(compiler_api)
println('Compiled examples/toml.v successfully for ($level) $arch $compiler_exe_name')
}
}
}

View File

@ -42,11 +42,6 @@ jobs:
./v -os windows cmd/v
./v -os windows examples/2048/2048.v
- name: Compile to raw Android (non-graphic) compatible
run: |
# Test that V can compile non-graphic app to Android compatible code *without* using the -apk flag
./v -os android -gc none examples/toml.v
linux-cross:
runs-on: ubuntu-20.04
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
@ -64,6 +59,7 @@ jobs:
sudo apt-get install --quiet -y libssl-dev sqlite3 libsqlite3-dev
sudo apt-get install --quiet -y mingw-w64 wine-stable winetricks
## sudo apt-get install --quiet -y wine32
- name: Turn off the wine crash dialog
run: winetricks nocrashdialog
@ -95,12 +91,6 @@ jobs:
./v -os windows examples/2048/2048.v
ls -lart examples/2048/2048.exe
- name: toml.v can be compiled to raw Android C
run: |
# Test that V can compile non-graphic app to Android compatible code *without* using the -apk flag
./v -os android -gc none examples/toml.v
windows-cross:
runs-on: windows-2019
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'

View File

@ -48,3 +48,27 @@ jobs:
safe_name=$(echo "$example" | sed 's%/%-%' | sed 's%\.%-%' )
vab examples/$example -o apks/$safe_name.apk
done
v-compiles-os-android:
runs-on: ubuntu-20.04
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
- name: Build V
run: make && sudo ./v symlink
- name: Install vab
run: |
v install vab
v -g ~/.vmodules/vab
sudo ln -s ~/.vmodules/vab/vab /usr/local/bin/vab
- name: Run vab --help
run: vab --help
- name: Run vab doctor
run: vab doctor
- name: Check `v -os android` *without* -apk flag
run: .github/workflows/android_cross_compile.vsh

61
.woodpecker.yml 100644
View File

@ -0,0 +1,61 @@
platform: 'linux/amd64'
branches: ['master']
pipeline:
gen-vc:
# This is what the official CI uses as well
image: 'ubuntu:latest'
secrets:
- deploy_key
commands:
# Install necessary dependencies
- apt-get update -y && apt-get install openssh-client git build-essential -y
# Build the compiler
- make
# Run ssh-agent
- eval $(ssh-agent -s)
# Add ssh key
- echo "$DEPLOY_KEY" | tr -d '\r' | ssh-add -
# Create ssh dir with proper permissions
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# Configure git credentials
- git config --global user.email 'vbot@rustybever.be'
- git config --global user.name 'vbot'
# Verify SSH keys
- ssh-keyscan git.rustybever.be > ~/.ssh/known_hosts
# The following is copied over from the official repo's CI
# https://github.com/vlang/v/blob/master/.github/workflows/gen_vc.yml
- export "COMMIT_HASH=$(git rev-parse --short HEAD)"
- export "COMMIT_MSG=$(git log -1 --oneline --pretty='%s' HEAD)"
- rm -rf vc
- git clone --depth=1 'git@git.rustybever.be:vieter/vc.git'
- rm -rf vc/v.c vc/v_win.c
- ./v -o vc/v.c -os cross cmd/v
- ./v -o vc/v_win.c -os windows -cc msvc cmd/v
- sed -i "1s/^/#define V_COMMIT_HASH \"$COMMIT_HASH\"\n/" vc/v.c
- sed -i "1s/^/#define V_COMMIT_HASH \"$COMMIT_HASH\"\n/" vc/v_win.c
# ensure the C files are over 5000 lines long, as a safety measure
- '[ $(wc -l < vc/v.c) -gt 5000 ]'
- '[ $(wc -l < vc/v_win.c) -gt 5000 ]'
- git -C vc add v.c v_win.c
- 'git -C vc commit -m "[v:master] $COMMIT_HASH - $COMMIT_MSG"'
# in case there are recent commits:
- git -C vc pull --rebase origin main
- git -C vc push
when:
event: push
publish:
image: woodpeckerci/plugin-docker-buildx
secrets: [ docker_username, docker_password ]
settings:
repo: chewingbever/vlang
tag: latest
dockerfile: Dockerfile.builder
platforms: [ linux/arm64/v8, linux/amd64 ]
# The build can run every time, because we should only push when there's
# actual changes
when:
event: push

View File

@ -0,0 +1,32 @@
matrix:
PLATFORM:
- 'linux/amd64'
- 'linux/arm64'
platform: ${PLATFORM}
branches: ['master']
depends_on:
- 'vc'
pipeline:
build:
image: 'menci/archlinuxarm:base-devel'
commands:
# Update packages
- pacman -Syu --noconfirm
# Create non-root user to perform build & switch to their home
- groupadd -g 1000 builder
- useradd -mg builder builder
- chown -R builder:builder "$PWD"
- "echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers"
- su builder
# Build the package
- makepkg -s --noconfirm --needed
publish:
image: 'curlimages/curl'
secrets:
- 'vieter_api_key'
commands:
# Publish the package
- 'for pkg in $(ls -1 *.pkg*); do curl -f -XPOST -T "$pkg" -H "X-API-KEY: $VIETER_API_KEY" https://arch.r8r.be/vieter/publish; done'

View File

@ -0,0 +1,18 @@
platform: 'linux/amd64'
branches: ['master']
depends_on:
- 'vc'
pipeline:
build-publish:
image: 'woodpeckerci/plugin-docker-buildx'
secrets: [ docker_username, docker_password ]
settings:
repo: chewingbever/vlang
tag: latest
dockerfile: Dockerfile.builder
platforms: [ linux/arm64/v8, linux/amd64 ]
# The build can run every time, because we should only push when there's
# actual changes
when:
event: push

View File

@ -0,0 +1,48 @@
platform: 'linux/amd64'
branches: ['master']
pipeline:
gen-vc:
# This is what the official CI uses as well
image: 'ubuntu:latest'
secrets:
- deploy_key
commands:
# Install necessary dependencies
- apt-get update -y && apt-get install openssh-client git build-essential -y
# Build the compiler
- make
# Run ssh-agent
- eval $(ssh-agent -s)
# Add ssh key
- echo "$DEPLOY_KEY" | tr -d '\r' | ssh-add -
# Create ssh dir with proper permissions
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# Configure git credentials
- git config --global user.email 'vbot@rustybever.be'
- git config --global user.name 'vbot'
# Verify SSH keys
- ssh-keyscan git.rustybever.be > ~/.ssh/known_hosts
# The following is copied over from the official repo's CI
# https://github.com/vlang/v/blob/master/.github/workflows/gen_vc.yml
- export "COMMIT_HASH=$(git rev-parse --short HEAD)"
- export "COMMIT_MSG=$(git log -1 --oneline --pretty='%s' HEAD)"
- rm -rf vc
- git clone --depth=1 'git@git.rustybever.be:vieter-v/vc.git'
- rm -rf vc/v.c vc/v_win.c
- ./v -o vc/v.c -os cross cmd/v
- ./v -o vc/v_win.c -os windows -cc msvc cmd/v
- sed -i "1s/^/#define V_COMMIT_HASH \"$COMMIT_HASH\"\n/" vc/v.c
- sed -i "1s/^/#define V_COMMIT_HASH \"$COMMIT_HASH\"\n/" vc/v_win.c
# ensure the C files are over 5000 lines long, as a safety measure
- '[ $(wc -l < vc/v.c) -gt 5000 ]'
- '[ $(wc -l < vc/v_win.c) -gt 5000 ]'
- git -C vc add v.c v_win.c
- 'git -C vc commit -m "[v:master] $COMMIT_HASH - $COMMIT_MSG"'
# in case there are recent commits:
- git -C vc pull --rebase origin main
- git -C vc push
when:
event: push

33
Dockerfile.builder 100644
View File

@ -0,0 +1,33 @@
FROM alpine:3.16
ARG TARGETPLATFORM
WORKDIR /opt/vlang
ENV VVV /opt/vlang
ENV PATH /opt/vlang:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV VFLAGS -cc gcc -gc none
ENV V_PATH /opt/vlang/v
RUN ln -s /opt/vlang/v /usr/bin/v && \
apk --no-cache add \
git make gcc curl openssl \
musl-dev \
openssl-libs-static openssl-dev \
zlib-static bzip2-static xz-dev expat-static zstd-static lz4-static \
sqlite-static sqlite-dev \
libx11-dev glfw-dev freetype-dev \
libarchive-static libarchive-dev \
diffutils \
mandoc
RUN git clone https://git.rustybever.be/vieter/v /opt/vlang && \
make && \
v -version
RUN if [ "$TARGETPLATFORM" = 'linux/amd64' ]; then \
wget -O /usr/local/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc && \
chmod +x /usr/local/bin/mc ; \
fi
CMD ["v"]

View File

@ -5,7 +5,7 @@ TMPDIR ?= /tmp
VROOT ?= .
VC ?= ./vc
V ?= ./v
VCREPO ?= https://github.com/vlang/vc
VCREPO ?= https://git.rustybever.be/vieter/vc
TCCREPO ?= https://github.com/vlang/tccbin
VCFILE := v.c

View File

@ -7,7 +7,7 @@ LDFLAGS ?=
all:
rm -rf vc/
git clone --depth 1 --quiet https://github.com/vlang/vc
git clone --depth 1 --quiet https://git.rustybever.be/vieter/vc
$(CC) $(CFLAGS) -std=gnu11 -w -o v1 vc/v.c -lm -lexecinfo -lpthread $(LDFLAGS)
./v1 -no-parallel -o v2 $(VFLAGS) cmd/v
./v2 -o v $(VFLAGS) cmd/v

54
PKGBUILD 100644
View File

@ -0,0 +1,54 @@
# Maintainer: Jef Roosens
# This PKGBUILD is mostly copied over from the AUR
# https://aur.archlinux.org/packages/vlang-git
pkgname=vieter-v
pkgver=0.2.2.r796.gfbc02cbc5
pkgrel=1
pkgdesc='Simple, fast, safe, compiled language for developing maintainable software'
arch=('x86_64' 'aarch64')
url='https://vlang.io'
license=('MIT')
depends=('glibc')
makedepends=('git')
optdepends=('glfw: Needed for graphics support'
'freetype2: Needed for graphics support'
'openssl: Needed for http support')
provides=('vlang')
conflicts=('v' 'vlang' 'vlang-bin')
source=('vlang::git+https://git.rustybever.be/Chewing_Bever/v')
sha256sums=('SKIP')
pkgver() {
cd "${srcdir}/vlang"
# Weekly tags are considered older than semantic tags that are older than
# them, so to prevent version resolution problems we exclude weekly tags.
git describe --long --tags --exclude "weekly*" | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
}
build() {
cd "${srcdir}/vlang"
# We don't require optimizations when compiling the bootstrap executable and
# -O2 actually breaks `./v self` (resulting in "cgen error:"), so we empty
# CFLAGS and LDFLAGS to ensure successful compilation.
CFLAGS="" LDFLAGS="" prod=1 make
# vpm and vdoc fail to compile with "unsupported linker option" when LDFLAGS
# is set
LDFLAGS="" ./v build-tools
}
package() {
cd "${srcdir}/vlang"
install -d "$pkgdir/usr/lib/vlang" "$pkgdir/usr/share/vlang" "$pkgdir/usr/bin"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm755 v "$pkgdir/usr/lib/vlang"
cp -a cmd "$pkgdir/usr/lib/vlang/"
cp -a examples "$pkgdir/usr/share/vlang/"
cp -a thirdparty "$pkgdir/usr/lib/vlang/"
cp -a vlib "$pkgdir/usr/lib/vlang/"
cp v.mod "$pkgdir/usr/lib/vlang/"
ln -s /usr/lib/vlang/v "$pkgdir/usr/bin/v"
touch "$pkgdir/usr/lib/vlang/cmd/tools/.disable_autorecompilation"
}

View File

@ -27,7 +27,7 @@
- Easy to develop: V compiles itself in less than a second
- Performance: as fast as C (V's main backend compiles to human-readable C)
- Safety: no null, no globals, no undefined behavior, immutability by default
- C to V translation
- C to V translation ([Translating DOOM demo video](https://www.youtube.com/watch?v=6oXrz3oRoEg))
- Hot code reloading
- [Innovative memory management](https://vlang.io/#memory) ([demo video](https://www.youtube.com/watch?v=gmB8ea8uLsM))
- [Cross-platform UI library](https://github.com/vlang/ui)

View File

@ -5,17 +5,22 @@ module main
import os
import v.util
const vexe = os.getenv('VEXE')
fn main() {
vmodules := os.vmodules_dir()
c2v_dir := os.join_path(vmodules, 'c2v_alpha')
c2v_bin := os.join_path(c2v_dir, 'c2v')
c2v_dir := os.join_path(vmodules, 'c2v')
mut c2v_bin := os.join_path(c2v_dir, 'c2v')
$if windows {
c2v_bin += '.exe'
}
// Git clone c2v
if !os.exists(c2v_dir) {
println('C2V is not installed. Cloning C2V to $c2v_dir ...')
os.chdir(vmodules)?
res := os.execute('git clone --depth 1 git@github.com:/vlang/c2v_alpha.git')
res := os.execute('git clone https://github.com/vlang/c2v')
if res.exit_code != 0 {
eprintln('Failed to download C2V. Perhaps it is not released yet? Is it June 20 yet?')
eprintln('Failed to download C2V.')
exit(1)
}
}
@ -23,15 +28,15 @@ fn main() {
if !os.exists(c2v_bin) {
os.chdir(c2v_dir)?
println('Compiling c2v ...')
res2 := os.execute('v -keepc -g -experimental -o c2v .')
res2 := os.execute('${os.quoted_path(vexe)} -o ${os.quoted_path(c2v_bin)} -keepc -g -experimental .')
if res2.exit_code != 0 {
eprintln(res2.output)
eprintln('Failed to compile C2V. This should never happen, please report it via GitHub.')
eprintln('Failed to compile C2V. This should not happen. Please report it via GitHub.')
exit(2)
}
}
if os.args.len < 3 {
eprintln('Wrong number of args. Use `v translate file.c`.')
eprintln('Wrong number of arguments. Use `v translate file.c` .')
exit(3)
}
passed_args := util.args_quote_paths(os.args[2..])

View File

@ -107,7 +107,7 @@ For more details and troubleshooting, please visit the [vab GitHub repository](h
</td><td width=33% valign=top>
* [Functions 2](#functions-2)
* [Pure functions by default](#pure-functions-by-default)
* [Immutable function args by default](#immutable-function-args-by-default)
* [Mutable arguments](#mutable-arguments)
* [Variable number of arguments](#variable-number-of-arguments)
* [Anonymous & higher-order functions](#anonymous--higher-order-functions)
@ -343,8 +343,7 @@ the expression `T(v)` converts the value `v` to the
type `T`.
Unlike most other languages, V only allows defining variables in functions.
Global (module level) variables are not allowed. There's no global state in V
(see [Pure functions by default](#pure-functions-by-default) for details).
Global (module level) variables are not allowed. There's no global state in V.
For consistency across different code bases, all variable and function names
must use the `snake_case` style, as opposed to type names, which must use `PascalCase`.
@ -2279,22 +2278,24 @@ Note that the embedded struct arguments are not necessarily stored in the order
## Functions 2
### Pure functions by default
### Immutable function args by default
V functions are pure by default, meaning that their return values are a function of their
arguments only, and their evaluation has no side effects (besides I/O).
In V function arguments are immutable by default, and mutable args have to be
marked on call.
This is achieved by a lack of global variables and all function arguments being
immutable by default, even when [references](#references) are passed.
Since there are also no globals, that means that the return values of the functions,
are a function of their arguments only, and their evaluation has no side effects
(unless the function uses I/O).
V is not a purely functional language however.
Function arguments are immutable by default, even when [references](#references) are passed.
Note that V is not a purely functional language however.
There is a compiler flag to enable global variables (`-enable-globals`), but this is
intended for low-level applications like kernels and drivers.
### Mutable arguments
It is possible to modify function arguments by using the keyword `mut`:
It is possible to modify function arguments by declaring them with the keyword `mut`:
```v
struct User {
@ -2313,7 +2314,7 @@ user.register()
println(user.is_registered) // "true"
```
In this example, the receiver (which is simply the first argument) is marked as mutable,
In this example, the receiver (which is just the first argument) is explicitly marked as mutable,
so `register()` can change the user object. The same works with non-receiver arguments:
```v
@ -3601,8 +3602,8 @@ println(compare(1.1, 1.2)) // -1
## Concurrency
### Spawning Concurrent Tasks
V's model of concurrency is very similar to Go's. To run `foo()` concurrently in
a different thread, just call it with `go foo()`:
V's model of concurrency is going to be very similar to Go's.
For now, `go foo()` runs `foo()` concurrently in a different thread:
```v
import math
@ -3618,6 +3619,9 @@ fn main() {
}
```
> In V 0.4 `go foo()` will be automatically renamed via vfmt to `spawn foo()`,
and there will be a way to launch a coroutine (a lightweight thread managed by the runtime).
Sometimes it is necessary to wait until a parallel thread has finished. This can
be done by assigning a *handle* to the started thread and calling the `wait()` method
to this handle later:

View File

@ -787,7 +787,7 @@ fn (mut app App) undo() {
fn (mut app App) on_key_down(key gg.KeyCode) {
// these keys are independent from the game state:
match key {
.a { app.is_ai_mode = !app.is_ai_mode }
.c { app.is_ai_mode = !app.is_ai_mode }
.escape { app.gg.quit() }
.n, .r { app.new_game() }
.backspace { app.undo() }
@ -797,12 +797,14 @@ fn (mut app App) on_key_down(key gg.KeyCode) {
else {}
}
if app.state in [.play, .freeplay] {
match key {
.w, .up { app.move(.up) }
.a, .left { app.move(.left) }
.s, .down { app.move(.down) }
.d, .right { app.move(.right) }
else {}
if !app.is_ai_mode {
match key {
.w, .up { app.move(.up) }
.a, .left { app.move(.left) }
.s, .down { app.move(.down) }
.d, .right { app.move(.right) }
else {}
}
}
}
if app.state == .victory {

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test file uploading data transformation</title>
</head>
<body>
<h2>File Upload and Data Transformation</h2>
Upload the <b>sample_input.txt</b> located in this example folder.
<br>
<br>
V will apply data transformation, adding the first two columns into a third column, and return the results as a download. <br>
<br>
For example:
<table style="color:blue;">
<tr>
<td>10</td>
<td>13</td>
</tr>
<tr>
<td>20</td>
<td>54</td>
</tr>
<tr>
<td>30</td>
<td>82</td>
</tr>
<tr>
<td>40</td>
<td>11</td>
</tr>
<tr>
<td>50</td>
<td>47</td>
</tr>
</table>
<br>
Becomes...
<br>
<table style="color:blue;">
<tr>
<td>10</td>
<td>13</td>
<td>23</td>
</tr>
<tr>
<td>20</td>
<td>54</td>
<td>74</td>
</tr>
<tr>
<td>30</td>
<td>82</td>
<td>112</td>
</tr>
<tr>
<td>40</td>
<td>11</td>
<td>51</td>
</tr>
<tr>
<td>50</td>
<td>47</td>
<td>97</td>
</tr>
</table>
<hr>
File form:
<form method="POST" enctype="multipart/form-data" action="http://localhost:8082/upload">
<input type="file" name="upfile"/>
<input type="submit" value="Upload & Transform"/>
</form>
</body>
</html>

View File

@ -0,0 +1,5 @@
10 13
20 54
30 82
40 11
50 47

View File

@ -0,0 +1,2 @@
<meta charset="utf-8">
<a href="/">Back</a>

View File

@ -0,0 +1,40 @@
module main
import vweb
const port = 8082
struct App {
vweb.Context
}
fn main() {
vweb.run(&App{}, port)
}
pub fn (mut app App) index() vweb.Result {
return $vweb.html()
}
['/upload'; post]
pub fn (mut app App) upload() vweb.Result {
fdata := app.files['upfile']
data_rows := fdata[0].data.split('\n')
mut output_data := ''
for elem in data_rows {
delim_row := elem.split('\t')
output_data += '${delim_row[0]}\t${delim_row[1]}\t${delim_row[0].int() + delim_row[1].int()}\n'
}
output_data = output_data.all_before_last('\n')
println(output_data)
app.add_header('Content-Disposition', 'attachment; filename=results.txt')
app.send_response_to_client('application/octet-stream', output_data)
return $vweb.html()
}

View File

@ -0,0 +1,338 @@
In this tutorial I'll demonstrate C2V, a fully automatic C to V source code translator,
that has just been released.
We'll run some simple examples and then translate the entire original DOOM,
the legendary shooter, the first ever 3D game released in 1993 and open sourced in 1997.
A video version of this tutorial is available on YouTube:
https://www.youtube.com/watch?v=6oXrz3oRoEg
Let's start.
### A simple C program
First, We have a simple C program that prints prime numbers. It's a small program,
but it has some of the most widely used language features:
function definitions, control flow via for loops and if conditions, a libc function call,
and various expressions.
```c
#include <stdio.h>
#include <stdbool.h>
bool is_prime(int x) {
for (int i = 2; i <= x / 2; i++) {
if (x % i == 0) return false;
}
return true;
}
int main() {
for (int i = 2; i < 100; i++) {
if (is_prime(i)) {
printf("%i\n", i);
}
}
return 0;
}
```
We can translate the C file to V by simply running
```
v translate primes.c
```
It will create `primes.v` with the following contents:
```v
[translated]
module main
fn is_prime(x int) bool {
for i := 2; i <= x / 2; i++ {
if x % i == 0 {
return false
}
}
return true
}
fn main() {
for i := 2; i < 100; i++ {
if is_prime(i) {
C.printf(c'%i\n', i)
}
}
return
}
```
This is a valid V program, and running it will give us the same result.
This code is pretty similar to the original, you can notice one major difference.
C allows one statement code blocks without brackets, and V doesn't, so in the
translated code all blocks are explicitely marked with brackets.
C2V successfully converts C's bool type to V's bool.
### Wrapper generation
C2V also has a wrapper generation mode.
For many projects it would make more sense not to translate the entire C code base,
but just to generate V wrappers on top of C APIs.
For example, let's say you have some complicated crypto or networking library in C
with lots of functions and types, and you don't want to manually type their definitions in V.
For this C2V has a wrapper mode.
We have a simple file `usersapi.h`. By running `v translate -wrapper usersapi.c`
we get `usersapi.v` with just the function definitions. Function bodies are skipped.
```c
#include <stdio.h>
void usersapi_create_user(char* name, char* password, int age) {
// ...
puts("User created!");
}
int usersapi_get_number_of_users() {
return 1;
}
```
```
module usersapi
fn C.usersapi_create_user(name &i8, password &i8, age int)
pub fn f create_user(name &i8, password &i8, age int) {
C.usersapi_create_user(name, password, age)
}
fn C.usersapi_get_number_of_users() int
pub fn f get_number_of_users() int {
return C.usersapi_get_number_of_users()
}
```
C doesn't have modules, so in most C libraries all functions start with the same prefix,
the name of the virtual module. C2V helpfully strips those prefixes and generates nice
clean wrapper functions that can be called with `usersapi.create_user()` instead of
`usersapi.usersapi_create_user()`
Perhaps in the future C2V will translate C strings (char pointers) to V strings as well.
### Translating DOOM
All right, this works, and it's nice, but these are very trivial examples.
Let's try something a lot more fun, like the DOOM game.
We think it's very important to have large functioning examples to demonstrate the power
of the tools but also to have something to test via CI to avoid regressions.
During V's release we had lots of various examples, now for C2V we have DOOM.
This is the primary test of C2V.
DOOM is a very complex project that uses some really tricky elements of the C language.
And making sure C2V can always handle DOOM not only serves as a good C2V test but also
as a test of the V compiler itself, since it can run entire DOOM in pure V.
First let's download the DOOM source and build it to make sure everything works.
The original 1997 open source release doesn't work nicely on modern OSs, so the community
has been working on different forks. I'll be using Chocolate Doom, it's one of the most
popular forks, and its goal is to be as close to the original as possible
(they even keep the original bugs).
```bash
git clone https://github.com/vlang/doom
```
The main difference in this fork is using SDL for cross platform rendering, so we'll need
to install SDL2 before we can build it.
A great thing about DOOM is that it has a very simple structure and is easy to build.
It's just a bunch of C files that are compiled into object files and are later linked
into a single executable.
To build the C version:
```bash
cd doom/chocolate-doom
cmake .
make chocolate-doom
```
We'll also need a freely distributed shareware doom1.wad with game resources and
the first free levels.
Now let's run the compiled game
```bash
./src/chocolate-doom -width 640
```
Great, as expected, it all works, now let's translate it to V and try to build that.
In the previous example we translated a single file, this time we'll translate
an entire directory.
```
v translate src/doom
```
This creates a new directory `src/doom/doom_v/`.
And we can use any name we want, there's a c2v configuration file.
`ll src/doom/doom_v`
It has a bunch of files translated from C to V.
We can open any file, for example g_game.c and verify that it is indeed DOOM code
translated from C to V. It's also been nicely formatted by vfmt.
Now let's build it.
```bash
cd src/doom
sh build_doom_v.sh
```
I'll explain what we have in the shell script a bit later.
But first let's run the translated code!
The script compiles into the `src/doom/doomv` binary. Everything works. We have the music,
we can watch the demo, go to the settings, start the game, walk around and shoot enemies.
There's a slight rendering bug at the bottom, but it can be cleaned up by pressing tab twice
and forcing a re-render.
There's a bug somewhere in C2V, it has just been released and needs some fixes.
Now let's look at those shell scripts and see how we built the V translation of DOOM.
C2V doesn't understand makefiles yet, so we have to do things manually.
In the future making such scripts won't be necessary.
Since doom uses C libraries like textscreen, pcsound, and opl, and we are only translating
doom itself, we need to build doom.o and then link it with the rest of object files.
`build_doom_v.sh`:
```bash
export DOOM=~/code/doom/chocolate-doom
v -o doom_v/doom.o -w -translated doom_v
cc -o doomv \
$DOOM/src/doom/doom_v/doom.o \
$DOOM/src/CMakeFiles/chocolate-doom.dir/*.o \
$DOOM/textscreen/CMakeFiles/textscreen.dir/*.o \
$DOOM/pcsound/CMakeFiles/pcsound.dir/*.o \
$DOOM/opl/CMakeFiles/opl.dir/*.o \
$(sdl2-config --libs) -lSDL2_mixer -lSDL2_net -lpng $LDFLAGS
```
The first line compiles all V files in `doom_v/` directory into a single doom.o object file.
`-translated` tells V that the code was translated from C, so the compiler is less strict.
For example it allows using `i++` as an expression (in V it always must be a statement).
In the future C2V will be able to translate such C expressions into valid V code,
and `-translated` will be removed entirely.
After generating doom.o, we can compile the entire project.
The second line links it with the rest of the object files.
Here we have the shared library used by other games in the chocolate doom project:
Hexen, Heretic, and Strife.
Then textscreen, pcsound, and opl.
In the near future we'll also translate all these C libraries DOOM depends on, and this
shell script and interacting with the C compiler won't be needed. The entire project will
be runnable with a simple `v translate src/doom && v .`
One other thing I wanted to do is to show you that it actually is V code we are running.
Let's go to `p_enemy.v` and modify the `P_Move` function. We can add a simple println with
some V specific code, like a reversed V array.
`println([1, 2, 3, 4, 5].reverse())`
Let's rebuild it and run again. As expected, we're getting our array printed every time an
enemy moves.
### Replacing C with V file by file
We translated the entire project. But sometimes it may be better to do more gradual transition
from C to V. For example, if you want to move your project from C to V, you might prefer doing
it slowly and carefully, file by file. After each file migration you can run tests and see if
your application runs correctly.
You can do that with c2v. All you need to do is just translate a single file, generate an object
file, and replace your current C object file with the new V object file.
We can do this with DOOM too. In fact that's how we made the entire thing work. We were doing it
for every single file, one by one, fixing compilation errors, bugs in C2V, and runtime bugs.
```
c2v -o p_enemy.o p_enemy.v
cp p_enemy.o ...
make clean
make chocolate-doom
```
It is actually amazing how well it all works considering there have been two translation
processes. From C to V via C2V and then From V back to C again via the V compiler.
In fact we can actually look at the generated C code after all the translation and compare it
to the original. Very clean and barely changed:
![image](https://user-images.githubusercontent.com/687996/175178560-58e04248-bcb3-4e33-aebd-ad66bb94a887.png)
Both C2V and V generate clean human readable code.
The comments and C defines are missing right now, that's obviously something very nice to have,
so they will be translated as well.
### Conclusion
C2V has just been released on June 22th 2022, so it's going to mature with the help of
the community. With bug fixes and new C features supported, it should support the
entire C standard soon.
Luckily C is a relatively small language. Any kind of software written in C will be
translatable to V fully automatically.
I will also be publishing the same demo with Sqlite and Quake translation.
It's a huge milestone for V and gives V developers access to huge amounts of software
written in C.
We're very excited about this release.

View File

@ -1130,6 +1130,23 @@ pub fn (s string) contains_any(chars string) bool {
return false
}
// contains_only returns `true`, if the string contains only the characters in `chars`.
pub fn (s string) contains_only(chars string) bool {
if chars.len == 0 {
return false
}
for ch in s {
mut res := 0
for i := 0; i < chars.len && res == 0; i++ {
res += int(ch == unsafe { chars.str[i] })
}
if res == 0 {
return false
}
}
return true
}
// contains_any_substr returns `true` if the string contains any of the strings in `substrs`.
pub fn (s string) contains_any_substr(substrs []string) bool {
if substrs.len == 0 {

View File

@ -447,6 +447,13 @@ fn test_contains_any() {
assert !''.contains_any('')
}
fn test_contains_only() {
assert '23885'.contains_only('0123456789')
assert '23gg885'.contains_only('01g23456789')
assert !'hello;'.contains_only('hello')
assert !''.contains_only('')
}
fn test_contains_any_substr() {
s := 'Some random text'
assert s.contains_any_substr(['false', 'not', 'rand'])

View File

@ -0,0 +1,46 @@
module os
fn test_find_abs_path_of_executable() ? {
tfolder := join_path(temp_dir(), 'v', 'tests', 'filepath_test')
rmdir_all(tfolder) or {}
assert !is_dir(tfolder)
mkdir_all(tfolder)?
defer {
rmdir_all(tfolder) or {}
}
//
original_path := getenv('PATH')
original_wdir := getwd()
defer {
chdir(original_wdir) or {}
}
//
new_path := tfolder + path_delimiter + original_path
setenv('PATH', new_path, true)
//
mut myclang_file := 'myclang'
$if windows {
myclang_file += '.bat'
}
//
chdir(tfolder)?
write_file(myclang_file, 'echo hello')?
chmod(myclang_file, 0o0777)?
dump(real_path(myclang_file))
dump(is_executable(myclang_file))
defer {
rm(myclang_file) or {}
}
//
fpath := find_abs_path_of_executable('myclang') or {
assert false
return
}
dump(fpath)
//
setenv('PATH', original_path, true)
if x := find_abs_path_of_executable('myclang') {
eprintln('> find_abs_path_of_executable should have failed, but instead it found: $x')
assert false
}
}

View File

@ -404,7 +404,7 @@ pub fn is_executable(path string) bool {
// 04 Read-only
// 06 Read and write
p := real_path(path)
return exists(p) && p.ends_with('.exe')
return exists(p) && (p.ends_with('.exe') || p.ends_with('.bat') || p.ends_with('.cmd'))
}
$if solaris {
statbuf := C.stat{}

View File

@ -12,6 +12,8 @@ pub const (
args = []string{}
)
const executable_suffixes = ['']
fn get_path_delimiter() string {
delimiter := ':'
$if js_node {

View File

@ -452,27 +452,34 @@ fn error_failed_to_find_executable() IError {
return IError(&ExecutableNotFoundError{})
}
// find_exe_path walks the environment PATH, just like most shell do, it returns
// find_abs_path_of_executable walks the environment PATH, just like most shell do, it returns
// the absolute path of the executable if found
pub fn find_abs_path_of_executable(exepath string) ?string {
if exepath == '' {
return error('expected non empty `exepath`')
}
if is_abs_path(exepath) {
return real_path(exepath)
}
mut res := ''
path := getenv('PATH')
paths := path.split(path_delimiter)
for p in paths {
found_abs_path := join_path_single(p, exepath)
if exists(found_abs_path) && is_executable(found_abs_path) {
res = found_abs_path
break
for suffix in executable_suffixes {
fexepath := exepath + suffix
if is_abs_path(fexepath) {
return real_path(fexepath)
}
mut res := ''
path := getenv('PATH')
paths := path.split(path_delimiter)
for p in paths {
found_abs_path := join_path_single(p, fexepath)
$if trace_find_abs_path_of_executable ? {
dump(found_abs_path)
}
if exists(found_abs_path) && is_executable(found_abs_path) {
res = found_abs_path
break
}
}
if res.len > 0 {
return real_path(res)
}
}
if res.len > 0 {
return real_path(res)
}
return error_failed_to_find_executable()
}

View File

@ -1,41 +1,107 @@
module os
struct C.AAsset {
#include <asset_manager.h>
#include <asset_manager_jni.h>
pub enum AssetMode {
buffer = C.AASSET_MODE_BUFFER // Caller plans to ask for a read-only buffer with all data.
random = C.AASSET_MODE_RANDOM // Read chunks, and seek forward and backward.
streaming = C.AASSET_MODE_STREAMING // Read sequentially, with an occasional forward seek.
unknown = C.AASSET_MODE_UNKNOWN // No specific information about how data will be accessed.
}
struct C.ANativeActivity {
assetManager &AssetManager // Pointer to the Asset Manager instance for the application.
clazz voidptr // (jobject) The NativeActivity object handle.
env voidptr // (JNIEnv *) JNI context for the main thread of the app.
externalDataPath &char // Path to this application's external (removable/mountable) data directory.
instance voidptr // This is the native instance of the application.
internalDataPath &char // Path to this application's internal data directory.
obbPath &char // Available starting with Honeycomb: path to the directory containing the application's OBB files (if any).
sdkVersion int // The platform's SDK version code.
vm voidptr // (JavaVM *) The global handle on the process's Java VM.
}
// NativeActivity defines the native side of an android.app.NativeActivity.
pub type NativeActivity = C.ANativeActivity
struct C.AAssetManager {
}
struct C.ANativeActivity {
assetManager voidptr
}
// AssetManager provides access to an application's raw assets by creating Asset objects.
pub type AssetManager = C.AAssetManager
fn C.AAssetManager_open(&C.AAssetManager, &char, int) &C.AAsset
// open opens an Android `Asset`
pub fn (am &AssetManager) open(filename string, mode AssetMode) !&Asset {
asset := C.AAssetManager_open(am, filename.str, int(mode))
if isnil(asset) {
return error('file `$filename` not found')
}
return asset
}
struct C.AAsset {
}
pub type Asset = C.AAsset
fn C.AAsset_getBuffer(&C.AAsset) voidptr
// get_buffer returns a pointer to a buffer holding the entire contents of the asset.
pub fn (a &Asset) get_buffer() voidptr {
return C.AAsset_getBuffer(a)
}
fn C.AAsset_getLength(&C.AAsset) int
fn C.AAsset_read(&C.AAsset, voidptr, int) int
// get_length returns the total size of the asset data.
pub fn (a &Asset) get_length() int {
return C.AAsset_getLength(a)
}
fn C.AAsset_getLength64(&C.AAsset) i64
// get_length_64 returns the total size of the asset data.
// get_length_64 returns the size using a 64-bit number insted of 32-bit as `get_length`.
pub fn (a &Asset) get_length_64() i64 {
return C.AAsset_getLength64(a)
}
fn C.AAsset_read(&C.AAsset, voidptr, usize) int
// read attempts to read 'count' bytes of data from the current offset.
// read returns the number of bytes read, zero on EOF, or < 0 on error.
pub fn (a &Asset) read(buffer voidptr, count usize) int {
return C.AAsset_read(a, buffer, count)
}
fn C.AAsset_close(&C.AAsset)
pub fn read_apk_asset(file string) ?[]u8 {
// close closes the asset, freeing all associated resources.
pub fn (a &Asset) close() {
C.AAsset_close(a)
}
// read_apk_asset returns all the data located at `path`.
// `path` is expected to be relative to the `assets`
pub fn read_apk_asset(path string) ![]u8 {
$if apk {
act := &C.ANativeActivity(C.sapp_android_get_native_activity())
act := &NativeActivity(C.sapp_android_get_native_activity())
if isnil(act) {
return error('Could not get reference to Android activity')
return error('could not get reference to Android activity')
}
asset := C.AAssetManager_open(&C.AAssetManager(act.assetManager), file.str, C.AASSET_MODE_STREAMING)
if isnil(asset) {
return error('File `$file` not found')
}
len := C.AAsset_getLength(asset)
asset_manager := &AssetManager(act.assetManager)
asset := asset_manager.open(path, .streaming)!
len := asset.get_length()
buf := []u8{len: len}
for {
if C.AAsset_read(asset, buf.data, len) > 0 {
if asset.read(buf.data, usize(len)) > 0 {
break
}
}
C.AAsset_close(asset)
asset.close()
return buf
} $else {
return error(@FN + ' can only be used with APK/AAB packaged Android apps')

View File

@ -17,6 +17,8 @@ pub const (
path_delimiter = ':'
)
const executable_suffixes = ['']
const (
stdin_value = 0
stdout_value = 1

View File

@ -14,6 +14,8 @@ fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) int
fn C._getpid() int
const executable_suffixes = ['.exe', '.bat', '.cmd', '']
pub const (
path_separator = '\\'
path_delimiter = ';'

View File

@ -657,10 +657,10 @@ pub fn (mut g Gen) init() {
g.cheaders.writeln('#include <stddef.h>')
} else {
tcc_undef_has_include := '
#if defined(__TINYC__) && defined(__has_include)
// tcc does not support has_include properly yet, turn it off completely
#undef __has_include
#endif'
#if defined(__TINYC__) && defined(__has_include)
// tcc does not support has_include properly yet, turn it off completely
#undef __has_include
#endif'
g.cheaders.writeln(tcc_undef_has_include)
g.includes.writeln(tcc_undef_has_include)
if g.pref.os == .freebsd {

View File

@ -111,7 +111,9 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) {
is_var_mut := expr.is_auto_deref_var()
str_fn_name := g.get_str_fn(typ)
if is_ptr && !is_var_mut {
g.write('str_intp(1, _MOV((StrIntpData[]){{_SLIT("&"), $si_s_code ,{.d_s=')
g.write('str_intp(1, _MOV((StrIntpData[]){{_SLIT("&"), $si_s_code ,{.d_s = isnil(')
g.expr(expr)
g.write(') ? _SLIT("nil") : ')
}
g.write('${str_fn_name}(')
if str_method_expects_ptr && !is_ptr {

View File

@ -0,0 +1,17 @@
import os
const vexe = os.getenv('VEXE')
const vroot = os.dir(vexe)
fn test_pkgconfig_can_be_compiled() ? {
tmp_exe := os.join_path(os.temp_dir(), '${os.getpid()}_pkgconfig.exe')
pkgconfig_v_file := os.real_path(os.join_path(vroot, 'vlib/v/pkgconfig/bin/pkgconfig.v'))
assert !os.exists(tmp_exe)
res := os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(tmp_exe)} ${os.quoted_path(pkgconfig_v_file)}')
if res != 0 {
assert false
}
assert os.exists(tmp_exe)
os.rm(tmp_exe)?
}

View File

@ -3,7 +3,7 @@ module pkgconfig
import flag
import strings
struct Main {
pub struct Main {
pub mut:
opt &MainOptions
res string

View File

@ -0,0 +1,11 @@
struct Node {
val int
left &Node
right &Node
}
fn test_string_ref_struct() {
n := Node{123, 0, 0}
println(n.left)
assert '$n.left' == '&nil'
}