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

@@ -0,0 +1,337 @@
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 explicitly 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;
}
```
```v oksyntax
module usersapi
fn C.usersapi_create_user(name &i8, password &i8, age int)
pub fn 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 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.
@@ -0,0 +1,432 @@
# Building a 400 KB Web Blog in V & SQLite with Veb
Hello,
In this guide, we'll build a simple web blog using V and the Veb framework.
The benefits of using V and Veb for web development:
- A safe, fast language with the development agility of Python or Ruby and the performance of C
- Zero dependencies: everything needed comes in a 1 MB package
- Very small binaries: this blog will be about 150 KB
- Easy deployments: single binary including precompiled templates
- Runs on minimal hardware: $3 instance sufficient for most apps
- Fast development with minimal boilerplate
> [!NOTE]
> V and Veb are at an early stage and evolving rapidly.
The code is available [here](./code/blog).
## Installing V
```
wget --quiet https://github.com/vlang/v/releases/latest/download/v_linux.zip
unzip v_linux.zip
cd v
sudo ./v symlink
```
V should now be globally available.
> On macOS use `v_macos.zip`, on Windows - `v_windows.zip`.
> For BSD, Solaris, Android, or source installation, see:
> https://github.com/vlang/v#installing-v-from-source
## Install SQLite Development Dependency
See [`sqlite` README](../../vlib/db/sqlite/README.md) for instructions if not already installed.
## Creating a New Veb Project
V projects can be created anywhere:
```bash
mkdir blog
cd blog
touch blog.v
```
First, let's create a simple hello world website:
```v oksyntax
// blog.v
module main
import veb
pub struct Context {
veb.Context
}
pub struct App {}
fn main() {
mut app := &App{}
veb.run[App, Context](mut app, 8081)
}
pub fn (app &App) index(mut ctx Context) veb.Result {
return ctx.text('Hello world from Veb!')
}
```
Run it with
```bash
v run blog.v
```
```
Running a Veb app on http://localhost:8081 ...
```
Veb helpfully provided a link, open http://localhost:8081/ in your browser:
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/hello.png?raw=true">
The App struct holds shared application data, while Context handles per-request data and embeds
`veb.Context` for response methods like `.text()`.
If you have experiencewith an MVC web framework, you can think of it as a controller. (Veb is
not an MVC framework however.)
As you can see, there are no routing rules. The `index()` action handles the `/` request by default.
Veb often uses convention over configuration and adding a new action requires
no routing rules either:
```v oksyntax
// blog.v
import veb
import time
fn (app &App) time(mut ctx Context) veb.Result {
return ctx.text(time.now().format())
}
```
Custom routes can be defined using attributes like @['/index'].
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/time.png?raw=true">
> TIP: run the following command to live-reload the server: `v watch run blog.v`
The `.text(string)` method returns a plain text document with the provided
text, which isn't frequently used in websites.
### HTML View
Let's return an HTML view instead. Create `index.html` in the same directory:
```html
<html>
<head>
<title>V Blog</title>
</head>
<body>
<b>@message</b>
<br />
<img src="https://vlang.io/img/v-logo.png" width="100" />
</body>
</html>
```
and update our `index()` action so that it returns the HTML view we just created:
```v ignore
// blog.v
pub fn (app &App) index(mut ctx Context) veb.Result {
message := 'Hello, world from Veb!'
return $veb.html()
}
```
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/hello_html.png?raw=true">
Good, now we have an actual HTML page.
The V template language is similar to C#'s Razor: `@message` prints the value
of `message`.
You may notice something unusual: the `message` variable created in the `index()`
action is automatically available in the view.
It's another feature of Veb to reduce the boilerplate in your web apps.
No need to create view models just to pass data, or use an unsafe and untyped
alternative, like C#'s `ViewBag["message"]`.
Making all action variables available in the view may seem crazy,
but V is a language with pure functions by default, and you won't be able
to modify any data from a view. `<b>@foo.bar()</b>` will only work if the `bar()` method
doesn't modify `foo`.
The HTML template is compiled to V during the compilation of the website,
that's done by the `$veb.html()` line.
(`$` always means compile time actions in V.) offering the following benefits:
- Great performance, since the templates don't need to be compiled
on every request, like in almost every major web framework.
- Easier deployment, since all your HTML templates are compiled
into a single binary file together with the web application itself.
- All errors in the templates are guaranteed to be caught during compilation.
### Fetching data with V ORM
Now let's display some articles!
We'll be using [V's built-in ORM](https://docs.vlang.io/orm.html) and a SQLite database.
Add a SQLite handle to `App`:
```v oksyntax
// blog.v
import db.sqlite
import veb
struct App {
// ...
db sqlite.DB
// ...
}
```
In `fn main()` we'll connect to a database.
Code in the `main()` function is run only once during app's startup, so we are going
to have one DB connection for all requests. This improves the performance of the web application,
since a DB connection doesn't have to be set up for each request.
```v oksyntax
import veb
// blog.v
fn main() {
mut app := &App{
db: sqlite.connect(':memory:')!
}
sql app.db {
create table Article
}!
first_article := Article{
title: 'Hello, world!'
text: 'V is great.'
}
second_article := Article{
title: 'Second post.'
text: 'Hm... what should I write about?'
}
sql app.db {
insert first_article into Article
insert second_article into Article
}!
veb.run[App, Context](mut app, 8081)
}
```
Create a new file `article.v`:
```v oksyntax
// article.v
module main
struct Article {
id int @[primary; sql: serial]
title string
text string
}
pub fn (app &App) find_all_articles() []Article {
return sql app.db {
select from Article
} or { panic(err) }
}
```
Notice that the `Article` structure conforms to the same structure and naming as
the database table in the creation SQL statement. Also we need to add ORM decorators
to our primary key to let it know that it is the primary key and it should auto-increment
Let's fetch the articles in the `index()` action:
```v ignore
// blog.v
pub fn (app &App) index() veb.Result {
articles := app.find_all_articles()
return $veb.html()
}
```
Finally, let's update our view:
```html
<body>
@for article in articles
<div>
<b>@article.title</b> <br />
@article.text
</div>
@end
</body>
```
```bash
v run .
```
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/articles1.png?raw=true">
That was very simple, wasn't it?
The built-in V ORM uses a syntax very similar to SQL. The queries are built with V.
For example, if we only wanted to find articles with ids between 100 and 200, we'd do:
```v oksyntax
// article.v
return sql app.db {
select from Article where id >= 100 && id <= 200
} or { panic(err) }
```
Retrieving a single article is very simple:
```v oksyntax
// article.v
pub fn (app &App) retrieve_article() ?Article {
return sql app.db {
select from Article limit 1
} or { panic(err) }[0]
}
```
V ORM uses V's optionals for single values, which is very useful, since
bad queries will always be handled by the developer:
```v ignore
// article.v
article := app.retrieve_article() or {
return ctx.text('Article not found')
}
```
### Adding new articles
Create `new.html`:
```html
<html>
<head>
<title>V Blog</title>
</head>
<body>
<form action="/new_article" method="post">
<input type="text" placeholder="Title" name="title" /> <br />
<textarea placeholder="Text" name="text"></textarea>
<input type="submit" />
</form>
</body>
</html>
```
```v ignore
@['/new']
pub fn (app &App) new(mut ctx Context) veb.Result {
return $veb.html()
}
@['/new_article'; post]
pub fn (app &App) new_article(mut ctx Context) veb.Result {
title := ctx.form['title'] or { '' }
text := ctx.form['text'] or { '' }
if title == '' || text == '' {
return ctx.text('Empty text/title')
}
article := Article{
title: title
text: text
}
sql app.db {
insert article into Article
} or { panic(err) }
return ctx.redirect('/')
}
```
The decorator on our function tells Veb that it is an HTTP POST type operation.
This time Veb parses the HTTP form and assigns correct values with correct types to
function arguments, which saves a lot of typing (e.g. `title := app.form['title']` is
not necessary).
We need to update `index.html` to add a link to the "new article" page:
```html
<a href="/new">New article</a>
```
Next we need to add the HTML endpoint to our code like we did with `index.html`:
```v ignore
@['/new']
pub fn (app &App) new() veb.Result {
return $veb.html()
}
```
Re-running this code will now allow us to add new posts to our blog endpoint
### JSON endpoints
This tutorial used the traditional server-side rendering. If you prefer
to render everything on the client or need an API, creating JSON endpoints
in V is very simple:
```v oksyntax
import veb
@['/articles'; get]
pub fn (app &App) articles(mut ctx Context) veb.Result {
articles := app.find_all_articles()
return ctx.json(articles)
}
```
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/articles_json.png?raw=true">
### Persistent data
If one wants to persist data they need to use a file instead of memory SQLite Database.
Replace the db setup code with this instead:
```
db: sqlite.connect('blog.db')!
```
As we can see it attempts to open a file in the current directory named `blog.db`.
If the database file doesn't exist it will create it. The second command will
create the table `Article` if none exists already. Now every time the
app is run you will see the articles created from the previous executions
### Building the blog
Run
```bash
v -d use_openssl -o blog -prod . && strip ./blog
```
This will result in a ~400KB binary. `-d use_openssl` tells Veb to link to OpenSSL.
Without this flag mbedtls will be embedded, and the binary size will increase to ~700KB.
### To be continued...
For an example of a more sophisticated web app written in V, check out Gitly: https://github.com/vlang/gitly
@@ -0,0 +1,13 @@
module main
struct Article {
id int @[primary; sql: serial]
title string
text string
}
pub fn (app &App) find_all_articles() []Article {
return sql app.db {
select from Article
} or { []Article{} }
}
@@ -0,0 +1,17 @@
drop table if exists Article;
create table Article (
id integer primary key,
title text default "",
text text default ""
);
insert into Article (title, text) values (
"Hello, world!",
"V is great."
);
insert into Article (title, text) values (
"Second post.",
"Hm... what should I write about?"
);
@@ -0,0 +1,90 @@
module main
import veb
import time
import db.sqlite
import json
// Context struct must embed veb.Context
pub struct Context {
veb.Context
pub mut:
user_id string
}
// App struct for shared data
pub struct App {
pub mut:
db sqlite.DB
}
// Main function
fn main() {
mut app := &App{
db: sqlite.connect('blog.db') or { panic(err) }
}
sql app.db {
create table Article
}!
// Use veb.run with App and Context types
veb.run[App, Context](mut app, 8081)
}
/*
// Middleware to run before each request
pub fn (mut ctx Context) before_request() bool {
ctx.user_id = ctx.get_cookie('id') or { '0' }
return true
}
*/
// Index endpoint
@['/index']
pub fn (app &App) index(mut ctx Context) veb.Result {
articles := app.find_all_articles()
return $veb.html()
}
// New article form endpoint
@['/new']
pub fn (app &App) new(mut ctx Context) veb.Result {
return $veb.html()
}
// Create new article endpoint
@['/new_article'; post]
pub fn (app &App) new_article(mut ctx Context) veb.Result {
title := ctx.form['title'] or { '' }
text := ctx.form['text'] or { '' }
if title == '' || text == '' {
return ctx.text('Empty text/title')
}
article := Article{
title: title
text: text
}
println('posting article')
println(article)
sql app.db {
insert article into Article
} or {}
return ctx.redirect('/')
}
// Get all articles endpoint
@['/articles'; get]
pub fn (app &App) articles(mut ctx Context) veb.Result {
articles := app.find_all_articles()
json_result := json.encode(articles)
return ctx.json(json_result)
}
// Time endpoint
@['/time']
pub fn (app &App) time(mut ctx Context) veb.Result {
return ctx.text(time.now().format())
}
@@ -0,0 +1,16 @@
<html>
<head>
<title>V Blog</title>
</head>
<body>
user id = @ctx.user_id <br>
@for article in articles
<div>
<b>@article.title</b> <br>
@article.text
</div>
<br>
@end
<a href='/new'>New article</a>
</body>
</html>
@@ -0,0 +1,13 @@
<html>
<head>
<title>V Blog</title>
</head>
<body>
<form action='/new_article' method='post'>
<input type='text' placeholder='Title' name='title'> <br>
<textarea placeholder='Text' name='text'></textarea>
<input type='submit'>
</form>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB