ci/tools: check-md.v: extract examples and check they are compilable (#6719)

pull/6729/head
Delyan Angelov 2020-11-03 02:04:14 +02:00 committed by GitHub
parent ae241785bf
commit f32c6784e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 376 additions and 97 deletions

View File

@ -19,5 +19,5 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Build V - name: Build V
run: make run: make
- name: Check docs line length - name: Check docs line length & code examples
run: ./v run cmd/tools/check-md.v doc/docs.md doc/upcoming.md CHANGELOG.md run: ./v run cmd/tools/check-md.v doc/docs.md doc/upcoming.md CHANGELOG.md

View File

@ -1,15 +1,21 @@
module main module main
import os import os
import rand
import term
import v.pref
const ( const (
too_long_line_length = 100 too_long_line_length = 100
term_colors = term.can_show_color_on_stderr()
) )
fn main() { fn main() {
files_paths := os.args[1..] files_paths := os.args[1..]
mut warnings := 0 mut warnings := 0
mut errors := 0 mut errors := 0
mut oks := 0
mut all_md_files := []MDFile{}
for file_path in files_paths { for file_path in files_paths {
real_path := os.real_path(file_path) real_path := os.real_path(file_path)
lines := os.read_lines(real_path) or { lines := os.read_lines(real_path) or {
@ -17,27 +23,207 @@ fn main() {
warnings++ warnings++
continue continue
} }
mut mdfile := MDFile{
path: file_path
}
for i, line in lines { for i, line in lines {
if line.len > too_long_line_length { if line.len > too_long_line_length {
linetrace_msg := '$file_path:${i + 1}:${line.len + 1}: '
if line.starts_with('|') { if line.starts_with('|') {
println(linetrace_msg + 'long table (warn)') println(wline(file_path, i, line.len, 'long table'))
warnings++ warnings++
} else if line.contains('https') { } else if line.contains('https') {
println(linetrace_msg + 'long link (warn)') println(wline(file_path, i, line.len, 'long link'))
warnings++ warnings++
} else { } else {
eprintln(linetrace_msg + 'line too long') eprintln(eline(file_path, i, line.len, 'line too long'))
errors++ errors++
} }
} }
mdfile.parse_line(i, line)
} }
all_md_files << mdfile
} }
if warnings > 0 || errors > 0 { for mut mdfile in all_md_files {
println('\nWarnings | Errors') new_errors, new_oks := mdfile.check_examples()
println('$warnings\t | $errors') errors += new_errors
oks += new_oks
}
// println('all_md_files: $all_md_files')
if warnings > 0 || errors > 0 || oks > 0 {
println('\nWarnings: $warnings | Errors: $errors | OKs: $oks')
} }
if errors > 0 { if errors > 0 {
exit(1) exit(1)
} }
} }
fn ftext(s string, cb fn (string) string) string {
if term_colors {
return cb(s)
}
return s
}
fn btext(s string) string {
return ftext(s, term.bold)
}
fn mtext(s string) string {
return ftext(s, term.magenta)
}
fn rtext(s string) string {
return ftext(s, term.red)
}
fn wline(file_path string, lnumber int, column int, message string) string {
return btext('$file_path:${lnumber + 1}:${column + 1}:') + btext(mtext(' warn:')) + rtext(' $message')
}
fn eline(file_path string, lnumber int, column int, message string) string {
return btext('$file_path:${lnumber + 1}:${column + 1}:') + btext(rtext(' error: $message'))
}
//
const (
default_command = 'compile'
)
struct VCodeExample {
mut:
text []string
command string
sline int
eline int
}
enum MDFileParserState {
markdown
vexample
}
struct MDFile {
path string
mut:
examples []VCodeExample
current VCodeExample
state MDFileParserState = .markdown
}
fn (mut f MDFile) parse_line(lnumber int, line string) {
if line.starts_with('```v') {
if f.state == .markdown {
f.state = .vexample
mut command := line.replace('```v', '').trim_space()
if command == '' {
command = default_command
}
f.current = VCodeExample{
sline: lnumber
command: command
}
}
return
}
if line.starts_with('```') && f.state == .vexample {
f.state = .markdown
f.current.eline = lnumber
f.examples << f.current
f.current = VCodeExample{}
return
}
if f.state == .vexample {
f.current.text << line
}
}
fn (mut f MDFile) dump() {
for e in f.examples {
eprintln('f.path: $f.path | example: $e')
}
}
fn (mut f MDFile) check_examples() (int, int) {
mut errors := 0
mut oks := 0
vexe := pref.vexe_path()
for e in f.examples {
if e.command == 'ignore' {
continue
}
if e.command == 'wip' {
continue
}
fname := os.base(f.path).replace('.md', '_md')
uid := rand.ulid()
vfile := os.join_path(os.temp_dir(), 'check_${fname}_example_${e.sline}__${e.eline}__${uid}.v')
mut should_cleanup_vfile := true
// eprintln('>>> checking example $vfile ...')
vcontent := e.text.join('\n')
os.write_file(vfile, vcontent) or {
panic(err)
}
mut acommands := e.command.split(' ')
for command in acommands {
match command {
'compile' {
res := os.system('"$vexe" -silent -o x.c $vfile')
os.rm('x.c') or { }
if res != 0 {
eprintln(eline(f.path, e.sline, 0, 'example failed to compile'))
eprintln(vcontent)
should_cleanup_vfile = false
errors++
continue
}
oks++
}
'failcompile' {
res := os.system('"$vexe" -silent -o x.c $vfile')
os.rm('x.c') or { }
if res == 0 {
eprintln(eline(f.path, e.sline, 0, '`failcompile` example compiled'))
eprintln(vcontent)
should_cleanup_vfile = false
errors++
continue
}
oks++
}
'oksyntax' {
res := os.system('"$vexe" -silent -check-syntax $vfile')
if res != 0 {
eprintln(eline(f.path, e.sline, 0, '`oksyntax` example with invalid syntax'))
eprintln(vcontent)
should_cleanup_vfile = false
errors++
continue
}
oks++
}
'badsyntax' {
res := os.system('"$vexe" -silent -check-syntax $vfile')
if res == 0 {
eprintln(eline(f.path, e.sline, 0, '`badsyntax` example can be parsed fine'))
eprintln(vcontent)
should_cleanup_vfile = false
errors++
continue
}
oks++
}
else {
eprintln(eline(f.path, e.sline, 0, 'unrecognized command: "$command", use one of: wip/ignore/compile/failcompile/oksyntax/badsyntax'))
should_cleanup_vfile = false
errors++
}
}
}
if should_cleanup_vfile {
os.rm(vfile) or {
panic(err)
}
}
}
return errors, oks
}

View File

@ -96,10 +96,20 @@ Anything you can do in other languages, you can do in V.
</td></tr> </td></tr>
</table> </table>
<!--
There are several special keywords, which you can put after the code fences for v.
These are:
compile - default, you do not need to specify it. cmd/tools/check-md.v compile the example.
ignore - ignore the example, useful for examples that just use the syntax highlighting
failcompile - known failing compilation. Useful for examples demonstrating compiler errors.
oksyntax - it should parse, it may not compile. Useful for partial examples.
badsyntax - known bad syntax, it should not even parse
wip - like ignore; a planned feature; easy to search.
-->
## Hello World ## Hello World
```v ```v
fn main() { fn main() {
println('hello world') println('hello world')
@ -265,7 +275,7 @@ Try compiling the program above after removing `mut` from the first line.
Note the (important) difference between `:=` and `=`. Note the (important) difference between `:=` and `=`.
`:=` is used for declaring and initializing, `=` is used for assigning. `:=` is used for declaring and initializing, `=` is used for assigning.
```v ```v failcompile
fn main() { fn main() {
age = 21 age = 21
} }
@ -304,7 +314,7 @@ that is already used in a parent scope will cause a compilation error.
### Primitive types ### Primitive types
```v ```v ignore
bool bool
string string
@ -331,7 +341,7 @@ on one side can be automatically promoted if it fits
completely into the data range of the type on the other side. completely into the data range of the type on the other side.
These are the allowed possibilities: These are the allowed possibilities:
``` ```v ignore
i8 → i16 → int → i64 i8 → i16 → int → i64
↘ ↘ ↘ ↘
f32 → f64 f32 → f64
@ -360,7 +370,7 @@ assert windows_newline.len == 2
In V, a string is a read-only array of bytes. String data is encoded using UTF-8. In V, a string is a read-only array of bytes. String data is encoded using UTF-8.
String values are immutable. You cannot mutate elements: String values are immutable. You cannot mutate elements:
```v ```v failcompile
mut s := 'hello 🌎' mut s := 'hello 🌎'
s[0] = `H` // not allowed s[0] = `H` // not allowed
``` ```
@ -411,6 +421,7 @@ println('[${int(x):-10}]') // pad with spaces on the right
### String operators ### String operators
```v ```v
name := 'Bob'
bobby := name + 'by' // + is used to concatenate strings bobby := name + 'by' // + is used to concatenate strings
println(bobby) // "Bobby" println(bobby) // "Bobby"
@ -421,7 +432,7 @@ println(s) // "hello world"
All operators in V must have values of the same type on both sides. All operators in V must have values of the same type on both sides.
You cannot concatenate an integer to a string: You cannot concatenate an integer to a string:
```v ```v failcompile
age := 10 age := 10
println('age = ' + age) // not allowed println('age = ' + age) // not allowed
``` ```
@ -430,12 +441,14 @@ println('age = ' + age) // not allowed
We have to either convert `age` to a `string`: We have to either convert `age` to a `string`:
```v ```v
age := 11
println('age = ' + age.str()) println('age = ' + age.str())
``` ```
or use string interpolation (preferred): or use string interpolation (preferred):
```v ```v
age := 12
println('age = $age') println('age = $age')
``` ```
@ -688,7 +701,7 @@ println('Your OS is ${os}.')
Any imported module name can be aliased using the `as` keyword: Any imported module name can be aliased using the `as` keyword:
NOTE: this example will not compile unless you have created `mymod/sha256.v` NOTE: this example will not compile unless you have created `mymod/sha256.v`
```v ```v failcompile
import crypto.sha256 import crypto.sha256
import mymod.sha256 as mysha256 import mymod.sha256 as mysha256
@ -712,7 +725,7 @@ fn (mut t MyTime) century() int {
} }
fn main() { fn main() {
my_time := MyTime{ mut my_time := MyTime{
year: 2020, year: 2020,
month: 12, month: 12,
day: 25 day: 25
@ -775,6 +788,11 @@ if x is Abc {
If you have a struct field which should be checked, there is also a way to name an alias. If you have a struct field which should be checked, there is also a way to name an alias.
```v ```v
struct MyStruct {x int}
struct MyStruct2 {y string}
type MySumType = MyStruct | MyStruct2
struct Abc { bar MySumType }
x := Abc{ bar: MyStruct{123} }
if x.bar is MyStruct as bar { if x.bar is MyStruct as bar {
// x.bar cannot be cast automatically // x.bar cannot be cast automatically
// you must explicitly state "as bar" to create a variable with the MyStruct type // you must explicitly state "as bar" to create a variable with the MyStruct type
@ -797,13 +815,16 @@ println('one' in m) // true
It's also useful for writing boolean expressions that are clearer and more compact: It's also useful for writing boolean expressions that are clearer and more compact:
```v ```v
enum Token { plus minus div mult }
struct Parser { token Token }
parser := Parser{}
if parser.token == .plus || parser.token == .minus || if parser.token == .plus || parser.token == .minus ||
parser.token == .div || parser.token == .mult { parser.token == .div || parser.token == .mult {
... // ...
} }
if parser.token in [.plus, .minus, .div, .mult] { if parser.token in [.plus, .minus, .div, .mult] {
... // ...
} }
``` ```
@ -998,15 +1019,18 @@ A defer statement defers the execution of a block of statements
until the surrounding function returns. until the surrounding function returns.
```v ```v
import os
fn read_log() { fn read_log() {
f := os.open('log.txt') or { panic(err) } mut ok := false
mut f := os.open('log.txt') or { panic(err) }
defer { f.close() } defer { f.close() }
... // ...
if !ok { if !ok {
// defer statement will be called here, the file will be closed // defer statement will be called here, the file will be closed
return return
} }
... // ...
// defer statement will be called here, the file will be closed // defer statement will be called here, the file will be closed
} }
``` ```
@ -1037,6 +1061,10 @@ Structs are allocated on the stack. To allocate a struct on the heap
and get a reference to it, use the `&` prefix: and get a reference to it, use the `&` prefix:
```v ```v
struct Point {
x int
y int
}
p := &Point{10, 10} p := &Point{10, 10}
// References have the same syntax for accessing fields // References have the same syntax for accessing fields
println(p.x) println(p.x)
@ -1049,7 +1077,7 @@ References are similar to Go pointers and C++ references.
V doesn't allow subclassing, but it supports embedded structs: V doesn't allow subclassing, but it supports embedded structs:
```v ```v wip
// TODO: this will be implemented later // TODO: this will be implemented later
struct Button { struct Button {
Widget Widget
@ -1085,6 +1113,10 @@ It's also possible to define custom default values.
### Short struct literal syntax ### Short struct literal syntax
```v ```v
struct Point{
x int
y int
}
mut p := Point{x: 10, y: 20} mut p := Point{x: 10, y: 20}
// you can omit the struct name when it's already known // you can omit the struct name when it's already known
@ -1108,11 +1140,17 @@ struct ButtonConfig {
height int = 20 height int = 20
} }
struct Button {
text string
width int
height int
}
fn new_button(c ButtonConfig) &Button { fn new_button(c ButtonConfig) &Button {
return &Button{ return &Button{
width: c.width width: c.width
height: c.height height: c.height
text: c.text text: c.text
} }
} }
@ -1123,7 +1161,7 @@ assert button.height == 20
As you can see, both the struct name and braces can be omitted, instead of: As you can see, both the struct name and braces can be omitted, instead of:
```v ```v ignore
new_button(ButtonConfig{text:'Click me', width:100}) new_button(ButtonConfig{text:'Click me', width:100})
``` ```
@ -1153,7 +1191,7 @@ __global:
For example, here's the `string` type defined in the `builtin` module: For example, here's the `string` type defined in the `builtin` module:
```v ```v ignore
struct string { struct string {
str byteptr str byteptr
pub: pub:
@ -1164,7 +1202,7 @@ pub:
It's easy to see from this definition that `string` is an immutable type. It's easy to see from this definition that `string` is an immutable type.
The byte pointer with the string data is not accessible outside `builtin` at all. The byte pointer with the string data is not accessible outside `builtin` at all.
The `len` field is public, but immutable: The `len` field is public, but immutable:
```v ```v failcompile
fn main() { fn main() {
str := 'hello' str := 'hello'
len := str.len // OK len := str.len // OK
@ -1222,6 +1260,7 @@ It is possible to modify function arguments by using the keyword `mut`:
```v ```v
struct User { struct User {
name string
mut: mut:
is_registered bool is_registered bool
} }
@ -1267,11 +1306,14 @@ instead of `register(mut user)`.
V makes it easy to return a modified version of an object: V makes it easy to return a modified version of an object:
```v ```v
struct User{ name string age int is_registered bool }
fn register(u User) User { fn register(u User) User {
return { u | is_registered: true } return { u | is_registered: true }
} }
mut user := User{name: 'abc' age: 23}
user = register(user) user = register(user)
println(user)
``` ```
### Anonymous & high order functions ### Anonymous & high order functions
@ -1304,12 +1346,13 @@ fn main() {
## References ## References
```v ```v
struct Foo{}
fn (foo Foo) bar_method() { fn (foo Foo) bar_method() {
... // ...
} }
fn bar_function(foo Foo) { fn bar_function(foo Foo) {
... // ...
} }
``` ```
@ -1324,6 +1367,8 @@ You can ensure that the struct is always passed by reference by
adding `&`: adding `&`:
```v ```v
struct Foo{ abc int }
fn (foo &Foo) bar() { fn (foo &Foo) bar() {
println(foo.abc) println(foo.abc)
} }
@ -1333,9 +1378,9 @@ fn (foo &Foo) bar() {
`(mut foo Foo)` must be used. `(mut foo Foo)` must be used.
In general, V's references are similar to Go pointers and C++ references. In general, V's references are similar to Go pointers and C++ references.
For example, a tree structure definition would look like this: For example, a generic tree structure definition would look like this:
```v ```v wip
struct Node<T> { struct Node<T> {
val T val T
left &Node left &Node
@ -1394,9 +1439,7 @@ They can represent complex structures, and this is used quite often since there
are no globals: are no globals:
--> -->
```v ```v ignore
println('Top cities: $TOP_CITIES.filter(.usa)')
vs
println('Top cities: $top_cities.filter(.usa)') println('Top cities: $top_cities.filter(.usa)')
``` ```
@ -1406,6 +1449,7 @@ println('Top cities: $top_cities.filter(.usa)')
strings, numbers, arrays, maps, structs. strings, numbers, arrays, maps, structs.
```v ```v
struct User{ name string age int }
println(1) // "1" println(1) // "1"
println('hi') // "hi" println('hi') // "hi"
println([1,2,3]) // "[1, 2, 3]" println([1,2,3]) // "[1, 2, 3]"
@ -1433,7 +1477,7 @@ If you don't want to print a newline, use `print()` instead.
The number of builtin functions is low. Other builtin functions are: The number of builtin functions is low. Other builtin functions are:
``` ```v ignore
fn exit(exit_code int) fn exit(exit_code int)
fn panic(message string) fn panic(message string)
fn print_backtrace() fn print_backtrace()
@ -1449,12 +1493,12 @@ quite easy to do.
To create a new module, create a directory with your module's name containing To create a new module, create a directory with your module's name containing
.v files with code: .v files with code:
``` ```shell
cd ~/code/modules cd ~/code/modules
mkdir mymodule mkdir mymodule
vim mymodule/myfile.v vim mymodule/myfile.v
``` ```
```v ```v failcompile
// myfile.v // myfile.v
module mymodule module mymodule
@ -1466,7 +1510,7 @@ pub fn say_hi() {
You can now use `mymodule` in your code: You can now use `mymodule` in your code:
```v ```v failcompile
import mymodule import mymodule
fn main() { fn main() {
@ -1574,14 +1618,19 @@ struct Venus {}
type World = Moon | Mars | Venus type World = Moon | Mars | Venus
sum := World(Moon{}) sum := World(Moon{})
println(sum)
``` ```
To check whether a sum type instance holds a certain type, use `sum is Type`. To check whether a sum type instance holds a certain type, use `sum is Type`.
To cast a sum type to one of its variants you can use `sum as Type`: To cast a sum type to one of its variants you can use `sum as Type`:
```v ```v
fn (m Mars) dust_storm() bool struct Moon {}
struct Mars {}
struct Venus {}
type World = Moon | Mars | Venus
fn (m Mars) dust_storm() bool { return true }
fn main() { fn main() {
mut w := World(Moon{}) mut w := World(Moon{})
assert w is Moon assert w is Moon
@ -1600,8 +1649,11 @@ fn main() {
You can also use `match` to determine the variant: You can also use `match` to determine the variant:
```v ```v
fn open_parachutes(n int) struct Moon {}
struct Mars {}
struct Venus {}
type World = Moon | Mars | Venus
fn open_parachutes(n int) { println(n) }
fn land(w World) { fn land(w World) {
match w { match w {
Moon {} // no atmosphere Moon {} // no atmosphere
@ -1624,9 +1676,15 @@ There are two ways to access the cast variant inside a match branch:
- using `as` to specify a variable name - using `as` to specify a variable name
```v ```v
fn (m Moon) moon_walk() struct Moon {}
fn (m Mars) shiver() struct Mars {}
fn (v Venus) sweat() struct Venus {}
type World = Moon | Mars | Venus
fn (m Moon) moon_walk() {}
fn (m Mars) shiver() {}
fn (v Venus) sweat() {}
fn pass_time(w World) { fn pass_time(w World) {
match w { match w {
@ -1700,7 +1758,7 @@ Unlike other languages, V does not handle exceptions with `throw/try/catch` bloc
`err` is defined inside an `or` block and is set to the string message passed `err` is defined inside an `or` block and is set to the string message passed
to the `error()` function. `err` is empty if `none` was returned. to the `error()` function. `err` is empty if `none` was returned.
```v ```v oksyntax
user := repo.find_user_by_id(7) or { user := repo.find_user_by_id(7) or {
println(err) // "User 7 not found" println(err) // "User 7 not found"
return return
@ -1730,7 +1788,7 @@ any further.
The body of `f` is essentially a condensed version of: The body of `f` is essentially a condensed version of:
```v ```v ignore
resp := http.get(url) or { resp := http.get(url) or {
return error(err) return error(err)
} }
@ -1740,7 +1798,7 @@ The body of `f` is essentially a condensed version of:
--- ---
The second method is to break from execution early: The second method is to break from execution early:
```v ```v oksyntax
user := repo.find_user_by_id(7) or { user := repo.find_user_by_id(7) or {
return return
} }
@ -1767,13 +1825,16 @@ fn do_something(s string) ?string {
a := do_something('foo') or { 'default' } // a will be 'foo' a := do_something('foo') or { 'default' } // a will be 'foo'
b := do_something('bar') or { 'default' } // b will be 'default' b := do_something('bar') or { 'default' } // b will be 'default'
println(a)
println(b)
``` ```
--- ---
The fourth method is to use `if` unwrapping: The fourth method is to use `if` unwrapping:
```v ```v
if resp := http.get(url) { import net.http
if resp := http.get('https://google.com') {
println(resp.text) // resp is a http.Response, not an optional println(resp.text) // resp is a http.Response, not an optional
} else { } else {
println(err) println(err)
@ -1784,7 +1845,8 @@ Above, `http.get` returns a `?http.Response`. `resp` is only in scope for the fi
## Generics ## Generics
```v ```v wip
struct Repo<T> { struct Repo<T> {
db DB db DB
} }
@ -1897,13 +1959,13 @@ variables:
```v ```v
fn f(ch chan int) { fn f(ch chan int) {
... // ...
} }
fn main() { fn main() {
... ch := chan int{}
go f(ch) go f(ch)
... // ...
} }
``` ```
@ -1911,6 +1973,8 @@ Objects can be pushed to channels using the arrow operator. The same operator ca
pop objects from the other end: pop objects from the other end:
```v ```v
mut ch := chan int{}
mut ch2 := chan f64{}
n := 5 n := 5
x := 7.3 x := 7.3
ch <- n // push ch <- n // push
@ -1927,9 +1991,12 @@ to do so will then result in a runtime panic (with the exception of `select` and
associated channel has been closed and the buffer is empty. This situation can be associated channel has been closed and the buffer is empty. This situation can be
handled using an or branch (see [Handling Optionals](#handling-optionals)). handled using an or branch (see [Handling Optionals](#handling-optionals)).
```v ```v wip
mut ch := chan int{}
mut ch2 := chan f64{}
// ...
ch.close() ch.close()
... // ...
m := <-ch or { m := <-ch or {
println('channel has been closed') println('channel has been closed')
} }
@ -1943,8 +2010,16 @@ y := <-ch2 ?
The `select` command allows monitoring several channels at the same time The `select` command allows monitoring several channels at the same time
without noticeable CPU load. It consists of a list of possible transfers and associated branches without noticeable CPU load. It consists of a list of possible transfers and associated branches
of statements - similar to the [match](#match) command: of statements - similar to the [match](#match) command:
```v ```v wip
select { import time
fn main () {
mut c := chan f64{}
mut ch := chan f64{}
mut ch2 := chan f64{}
mut ch3 := chan f64{}
mut b := 0.0
// ...
select {
a := <-ch { a := <-ch {
// do something with `a` // do something with `a`
} }
@ -1957,7 +2032,8 @@ select {
> 500 * time.millisecond { > 500 * time.millisecond {
// do something if no channel has become ready within 0.5s // do something if no channel has become ready within 0.5s
} }
} }
}
``` ```
The timeout branch is optional. If it is absent `select` waits for an unlimited amount of time. The timeout branch is optional. If it is absent `select` waits for an unlimited amount of time.
@ -1966,10 +2042,10 @@ by adding an `else { ... }` branch. `else` and `> timeout` are mutually exclusiv
The `select` command can be used as an *expression* of type `bool` The `select` command can be used as an *expression* of type `bool`
that becomes `false` if all channels are closed: that becomes `false` if all channels are closed:
```v ```v wip
if select { if select {
ch <- a { ch <- a {
... // ...
} }
} { } {
// channel was open // channel was open
@ -1982,10 +2058,21 @@ if select {
For special purposes there are some builtin properties and methods: For special purposes there are some builtin properties and methods:
```v ```v
struct Abc{x int}
a := 2.13
mut ch := chan f64{}
res := ch.try_push(a) // try to perform `ch <- a` res := ch.try_push(a) // try to perform `ch <- a`
res2 := ch2.try_pop(mut b) // try to perform `b = <-ch2 println(res)
l := ch.len // number of elements in queue l := ch.len // number of elements in queue
c := ch.cap // maximum queue length c := ch.cap // maximum queue length
println(l)
println(c)
// mut b := Abc{}
// mut ch2 := chan f64{}
// res2 := ch2.try_pop(mut b) // try to perform `b = <-ch2
``` ```
The `try_push/pop()` methods will return immediately with one of the results The `try_push/pop()` methods will return immediately with one of the results
`.success`, `.not_ready` or `.closed` - dependent on whether the object has been transferred or `.success`, `.not_ready` or `.closed` - dependent on whether the object has been transferred or
@ -2007,12 +2094,12 @@ mut:
} }
fn (mut b St) g() { fn (mut b St) g() {
...
b.mtx.m_lock() b.mtx.m_lock()
// read/modify/write b.x // read/modify/write b.x
...
b.mtx.unlock() b.mtx.unlock()
...
} }
fn caller() { fn caller() {
@ -2021,12 +2108,12 @@ fn caller() {
mtx: sync.new_mutex() mtx: sync.new_mutex()
} }
go a.g() go a.g()
...
a.mtx.m_lock() a.mtx.m_lock()
// read/modify/write a.x // read/modify/write a.x
...
a.mtx.unlock() a.mtx.unlock()
...
} }
``` ```
@ -2079,9 +2166,10 @@ No runtime reflection is used. This results in much better performance.
### Asserts ### Asserts
```v ```v
mut v := 2 fn foo(mut v []int) { v[0] = 1 }
mut v := [20]
foo(mut v) foo(mut v)
assert v < 4 assert v[0] < 4
``` ```
An `assert` statement checks that its expression evaluates to `true`. If an assert fails, An `assert` statement checks that its expression evaluates to `true`. If an assert fails,
the program will abort. Asserts should only be used to detect programming errors. When an the program will abort. Asserts should only be used to detect programming errors. When an
@ -2101,7 +2189,7 @@ fn main() {
println(hello()) println(hello())
} }
``` ```
```v ```v failcompile
module main module main
// hello_test.v // hello_test.v
fn test_hello() { fn test_hello() {
@ -2150,16 +2238,19 @@ during compilation. If your V program compiles, it's guaranteed that it's going
to be leak free. For example: to be leak free. For example:
```v ```v
fn draw_text(s string, x, y int) { import strings
... fn draw_text(s string, x int, y int) {
// ...
} }
fn draw_scene() { fn draw_scene() {
... // ...
name1 := 'abc'
name2 := 'def ghi'
draw_text('hello $name1', 10, 10) draw_text('hello $name1', 10, 10)
draw_text('hello $name2', 100, 10) draw_text('hello $name2', 100, 10)
draw_text(strings.repeat('X', 10000), 10, 50) draw_text(strings.repeat(`X`, 10000), 10, 50)
... // ...
} }
``` ```
@ -2171,6 +2262,7 @@ These two strings are small,
V will use a preallocated buffer for them. V will use a preallocated buffer for them.
```v ```v
struct User{ name string }
fn test() []int { fn test() []int {
number := 7 // stack variable number := 7 // stack variable
user := User{} // struct allocated on stack user := User{} // struct allocated on stack
@ -2200,6 +2292,7 @@ V's ORM provides a number of benefits:
then manually construct objects from the parsed results.) then manually construct objects from the parsed results.)
```v ```v
import sqlite
struct Customer { // struct name has to be the same as the table name (for now) struct Customer { // struct name has to be the same as the table name (for now)
id int // a field named `id` of integer type must be the first field id int // a field named `id` of integer type must be the first field
name string name string
@ -2207,7 +2300,7 @@ struct Customer { // struct name has to be the same as the table name (for now)
country string country string
} }
db := sqlite.connect('customers.db') db := sqlite.connect('customers.db')?
// select count(*) from Customer // select count(*) from Customer
nr_customers := sql db { select count from Customer } nr_customers := sql db { select count from Customer }
@ -2260,7 +2353,7 @@ To generate documentation use vdoc, for example `v doc net.http`.
You don't need to worry about formatting your code or setting style guidelines. You don't need to worry about formatting your code or setting style guidelines.
`v fmt` takes care of that: `v fmt` takes care of that:
```v ```shell
v fmt file.v v fmt file.v
``` ```
@ -2314,9 +2407,9 @@ Examples of potentially memory-unsafe operations are:
To mark potentially memory-unsafe operations, enclose them in an `unsafe` block: To mark potentially memory-unsafe operations, enclose them in an `unsafe` block:
```v ```v failcompile
// allocate 2 uninitialized bytes & return a reference to them // allocate 2 uninitialized bytes & return a reference to them
mut p := unsafe { &byte(malloc(2)) } mut p := unsafe { malloc(2) }
p[0] = `h` // Error: pointer indexing is only allowed in `unsafe` blocks p[0] = `h` // Error: pointer indexing is only allowed in `unsafe` blocks
unsafe { unsafe {
p[0] = `h` // OK p[0] = `h` // OK
@ -2409,7 +2502,7 @@ Currently the `linux`, `darwin` , `freebsd`, and `windows` flags are supported.
NB: Each flag must go on its own line (for now) NB: Each flag must go on its own line (for now)
```v ```v oksyntax
#flag linux -lsdl2 #flag linux -lsdl2
#flag linux -Ivig #flag linux -Ivig
#flag linux -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 #flag linux -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
@ -2428,7 +2521,7 @@ freedesktop one.
If no flags are passed it will add `--cflags` and `--libs`, both lines below do the same: If no flags are passed it will add `--cflags` and `--libs`, both lines below do the same:
```v ```v oksyntax
#pkgconfig r_core #pkgconfig r_core
#pkgconfig --cflags --libs r_core #pkgconfig --cflags --libs r_core
``` ```
@ -2445,7 +2538,7 @@ Then:
* Put a v.mod file inside the toplevel folder of your module (if you * Put a v.mod file inside the toplevel folder of your module (if you
created your module with `v new` you already have v.mod file). For created your module with `v new` you already have v.mod file). For
example: example:
```v ```v ignore
Module { Module {
name: 'mymodule', name: 'mymodule',
description: 'My nice module wraps a simple C library.', description: 'My nice module wraps a simple C library.',
@ -2456,7 +2549,7 @@ Module {
* Add these lines to the top of your module: * Add these lines to the top of your module:
```v ```v oksyntax
#flag -I @VROOT/c #flag -I @VROOT/c
#flag @VROOT/c/implementation.o #flag @VROOT/c/implementation.o
#include "header.h" #include "header.h"
@ -2605,7 +2698,7 @@ eprintln( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @MOD + '.' + @FN)
``` ```
Another example, is if you want to embed the version/name from v.mod *inside* your executable: Another example, is if you want to embed the version/name from v.mod *inside* your executable:
```v ```v ignore
import v.vmod import v.vmod
vm := vmod.decode( @VMOD_FILE ) or { panic(err) } vm := vmod.decode( @VMOD_FILE ) or { panic(err) }
eprintln('$vm.name $vm.version\n $vm.description') eprintln('$vm.name $vm.version\n $vm.description')
@ -2648,7 +2741,7 @@ the boolean expression is highly improbable. In the JS backend, that does nothin
Having built-in JSON support is nice, but V also allows you to create efficient Having built-in JSON support is nice, but V also allows you to create efficient
serializers for any data format. V has compile-time `if` and `for` constructs: serializers for any data format. V has compile-time `if` and `for` constructs:
```v ```v wip
// TODO: not fully implemented // TODO: not fully implemented
struct User { struct User {
@ -2732,7 +2825,7 @@ To improve safety and maintainability, operator overloading is limited:
TODO: not implemented yet TODO: not implemented yet
```v ```v failcompile
fn main() { fn main() {
a := 10 a := 10
asm x64 { asm x64 {
@ -2767,7 +2860,7 @@ int main() {
Run `v translate test.cpp` and V will generate `test.v`: Run `v translate test.cpp` and V will generate `test.v`:
```v ```v
fn main { fn main() {
mut s := []string{} mut s := []string{}
s << 'V is ' s << 'V is '
s << 'awesome' s << 'awesome'
@ -2825,13 +2918,13 @@ More examples, including a graphical application:
To cross compile your project simply run To cross compile your project simply run
```v ```shell
v -os windows . v -os windows .
``` ```
or or
```v ```shell
v -os linux . v -os linux .
``` ```
@ -2853,7 +2946,7 @@ cross-platform support. "V scripts" run on Unix-like systems as well as on Windo
Use the `.vsh` file extension. It will make all functions in the `os` Use the `.vsh` file extension. It will make all functions in the `os`
module global (so that you can use `ls()` instead of `os.ls()`, for example). module global (so that you can use `ls()` instead of `os.ls()`, for example).
```v ```v wip
#!/usr/local/bin/v run #!/usr/local/bin/v run
// The shebang above associates the file to V on Unix-like systems, // The shebang above associates the file to V on Unix-like systems,
// so it can be run just by specifying the path to the file // so it can be run just by specifying the path to the file
@ -2930,7 +3023,7 @@ fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)
V has 41 reserved keywords (3 are literals): V has 41 reserved keywords (3 are literals):
```v ```v ignore
as as
asm asm
assert assert
@ -2978,7 +3071,7 @@ See also [Types](#types).
This lists operators for [primitive types](#primitive-types) only. This lists operators for [primitive types](#primitive-types) only.
```v ```v ignore
+ sum integers, floats, strings + sum integers, floats, strings
- difference integers, floats - difference integers, floats
* product integers, floats * product integers, floats

View File

@ -22,7 +22,7 @@ Objects that are supposed to be used to exchange data between
coroutines have to be declared with special care. Exactly one of the following coroutines have to be declared with special care. Exactly one of the following
4 kinds of declaration has to be chosen: 4 kinds of declaration has to be chosen:
```v ```v ignore
a := ... a := ...
mut b := ... mut b := ...
shared c := ... shared c := ...
@ -41,14 +41,14 @@ atomic d := ...
*concurrently*.<sup>2</sup> In order to avoid data races it has to *concurrently*.<sup>2</sup> In order to avoid data races it has to
be locked before access can occur and unlocked to allow access to be locked before access can occur and unlocked to allow access to
other coroutines. This is done by one the following block structures: other coroutines. This is done by one the following block structures:
```v ```v ignore
lock c { lock c {
// read, modify, write c // read, modify, write c
... ...
} }
``` ```
```v ```v ignore
rlock c { rlock c {
// read c // read c
... ...
@ -122,7 +122,7 @@ Outside of `lock`/`rlock` blocks function arguments must in general
match - with the familiar exception that objects declared `mut` can be match - with the familiar exception that objects declared `mut` can be
used to call functions expecting immutable arguments: used to call functions expecting immutable arguments:
```v ```v ignore
fn f(x St) {...} fn f(x St) {...}
fn g(mut x St) {...} fn g(mut x St) {...}
fn h(shared x St) {...} fn h(shared x St) {...}
@ -145,7 +145,7 @@ i(atomic d)
Inside a `lock c {...}` block `c` behaves like a `mut`, Inside a `lock c {...}` block `c` behaves like a `mut`,
inside an `rlock c {...}` block like an immutable: inside an `rlock c {...}` block like an immutable:
```v ```v ignore
shared c := &St{...} shared c := &St{...}
lock c { lock c {
g(mut c) g(mut c)
@ -165,7 +165,7 @@ object is accessed outside of any corresponding `lock`/`rlock`
block. However in simple and obvious cases the necessary lock/unlock block. However in simple and obvious cases the necessary lock/unlock
can be generated automatically for `array`/`map` operations: can be generated automatically for `array`/`map` operations:
```v ```v ignore
shared a []int{...} shared a []int{...}
go h2(shared a) go h2(shared a)
a << 3 a << 3