ci/tools: check-md.v: extract examples and check they are compilable (#6719)
parent
ae241785bf
commit
f32c6784e7
|
@ -19,5 +19,5 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- name: Build V
|
||||
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
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
module main
|
||||
|
||||
import os
|
||||
import rand
|
||||
import term
|
||||
import v.pref
|
||||
|
||||
const (
|
||||
too_long_line_length = 100
|
||||
term_colors = term.can_show_color_on_stderr()
|
||||
)
|
||||
|
||||
fn main() {
|
||||
files_paths := os.args[1..]
|
||||
mut warnings := 0
|
||||
mut errors := 0
|
||||
mut oks := 0
|
||||
mut all_md_files := []MDFile{}
|
||||
for file_path in files_paths {
|
||||
real_path := os.real_path(file_path)
|
||||
lines := os.read_lines(real_path) or {
|
||||
|
@ -17,27 +23,207 @@ fn main() {
|
|||
warnings++
|
||||
continue
|
||||
}
|
||||
mut mdfile := MDFile{
|
||||
path: file_path
|
||||
}
|
||||
for i, line in lines {
|
||||
if line.len > too_long_line_length {
|
||||
linetrace_msg := '$file_path:${i + 1}:${line.len + 1}: '
|
||||
if line.starts_with('|') {
|
||||
println(linetrace_msg + 'long table (warn)')
|
||||
println(wline(file_path, i, line.len, 'long table'))
|
||||
warnings++
|
||||
} else if line.contains('https') {
|
||||
println(linetrace_msg + 'long link (warn)')
|
||||
println(wline(file_path, i, line.len, 'long link'))
|
||||
warnings++
|
||||
} else {
|
||||
eprintln(linetrace_msg + 'line too long')
|
||||
eprintln(eline(file_path, i, line.len, 'line too long'))
|
||||
errors++
|
||||
}
|
||||
}
|
||||
mdfile.parse_line(i, line)
|
||||
}
|
||||
all_md_files << mdfile
|
||||
}
|
||||
if warnings > 0 || errors > 0 {
|
||||
println('\nWarnings | Errors')
|
||||
println('$warnings\t | $errors')
|
||||
for mut mdfile in all_md_files {
|
||||
new_errors, new_oks := mdfile.check_examples()
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
|
251
doc/docs.md
251
doc/docs.md
|
@ -96,10 +96,20 @@ Anything you can do in other languages, you can do in V.
|
|||
</td></tr>
|
||||
</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
|
||||
|
||||
|
||||
```v
|
||||
fn main() {
|
||||
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 `=`.
|
||||
`:=` is used for declaring and initializing, `=` is used for assigning.
|
||||
|
||||
```v
|
||||
```v failcompile
|
||||
fn main() {
|
||||
age = 21
|
||||
}
|
||||
|
@ -304,7 +314,7 @@ that is already used in a parent scope will cause a compilation error.
|
|||
|
||||
### Primitive types
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
bool
|
||||
|
||||
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.
|
||||
These are the allowed possibilities:
|
||||
|
||||
```
|
||||
```v ignore
|
||||
i8 → i16 → int → i64
|
||||
↘ ↘
|
||||
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.
|
||||
String values are immutable. You cannot mutate elements:
|
||||
|
||||
```v
|
||||
```v failcompile
|
||||
mut s := 'hello 🌎'
|
||||
s[0] = `H` // not allowed
|
||||
```
|
||||
|
@ -411,6 +421,7 @@ println('[${int(x):-10}]') // pad with spaces on the right
|
|||
### String operators
|
||||
|
||||
```v
|
||||
name := 'Bob'
|
||||
bobby := name + 'by' // + is used to concatenate strings
|
||||
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.
|
||||
You cannot concatenate an integer to a string:
|
||||
|
||||
```v
|
||||
```v failcompile
|
||||
age := 10
|
||||
println('age = ' + age) // not allowed
|
||||
```
|
||||
|
@ -430,12 +441,14 @@ println('age = ' + age) // not allowed
|
|||
We have to either convert `age` to a `string`:
|
||||
|
||||
```v
|
||||
age := 11
|
||||
println('age = ' + age.str())
|
||||
```
|
||||
|
||||
or use string interpolation (preferred):
|
||||
|
||||
```v
|
||||
age := 12
|
||||
println('age = $age')
|
||||
```
|
||||
|
||||
|
@ -688,7 +701,7 @@ println('Your OS is ${os}.')
|
|||
Any imported module name can be aliased using the `as` keyword:
|
||||
|
||||
NOTE: this example will not compile unless you have created `mymod/sha256.v`
|
||||
```v
|
||||
```v failcompile
|
||||
import crypto.sha256
|
||||
import mymod.sha256 as mysha256
|
||||
|
||||
|
@ -712,7 +725,7 @@ fn (mut t MyTime) century() int {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
my_time := MyTime{
|
||||
mut my_time := MyTime{
|
||||
year: 2020,
|
||||
month: 12,
|
||||
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.
|
||||
```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 {
|
||||
// x.bar cannot be cast automatically
|
||||
// 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:
|
||||
|
||||
```v
|
||||
enum Token { plus minus div mult }
|
||||
struct Parser { token Token }
|
||||
parser := Parser{}
|
||||
if parser.token == .plus || parser.token == .minus ||
|
||||
parser.token == .div || parser.token == .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.
|
||||
|
||||
```v
|
||||
import os
|
||||
|
||||
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() }
|
||||
...
|
||||
// ...
|
||||
if !ok {
|
||||
// defer statement will be called here, the file will be closed
|
||||
return
|
||||
}
|
||||
...
|
||||
// ...
|
||||
// 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:
|
||||
|
||||
```v
|
||||
struct Point {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
p := &Point{10, 10}
|
||||
// References have the same syntax for accessing fields
|
||||
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
|
||||
```v wip
|
||||
// TODO: this will be implemented later
|
||||
struct Button {
|
||||
Widget
|
||||
|
@ -1085,6 +1113,10 @@ It's also possible to define custom default values.
|
|||
### Short struct literal syntax
|
||||
|
||||
```v
|
||||
struct Point{
|
||||
x int
|
||||
y int
|
||||
}
|
||||
mut p := Point{x: 10, y: 20}
|
||||
|
||||
// you can omit the struct name when it's already known
|
||||
|
@ -1108,6 +1140,12 @@ struct ButtonConfig {
|
|||
height int = 20
|
||||
}
|
||||
|
||||
struct Button {
|
||||
text string
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
fn new_button(c ButtonConfig) &Button {
|
||||
return &Button{
|
||||
width: c.width
|
||||
|
@ -1123,7 +1161,7 @@ assert button.height == 20
|
|||
|
||||
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})
|
||||
```
|
||||
|
||||
|
@ -1153,7 +1191,7 @@ __global:
|
|||
|
||||
For example, here's the `string` type defined in the `builtin` module:
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
struct string {
|
||||
str byteptr
|
||||
pub:
|
||||
|
@ -1164,7 +1202,7 @@ pub:
|
|||
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 `len` field is public, but immutable:
|
||||
```v
|
||||
```v failcompile
|
||||
fn main() {
|
||||
str := 'hello'
|
||||
len := str.len // OK
|
||||
|
@ -1222,6 +1260,7 @@ It is possible to modify function arguments by using the keyword `mut`:
|
|||
|
||||
```v
|
||||
struct User {
|
||||
name string
|
||||
mut:
|
||||
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
|
||||
struct User{ name string age int is_registered bool }
|
||||
fn register(u User) User {
|
||||
return { u | is_registered: true }
|
||||
}
|
||||
|
||||
mut user := User{name: 'abc' age: 23}
|
||||
user = register(user)
|
||||
println(user)
|
||||
```
|
||||
|
||||
### Anonymous & high order functions
|
||||
|
@ -1304,12 +1346,13 @@ fn main() {
|
|||
## References
|
||||
|
||||
```v
|
||||
struct Foo{}
|
||||
fn (foo Foo) bar_method() {
|
||||
...
|
||||
// ...
|
||||
}
|
||||
|
||||
fn bar_function(foo Foo) {
|
||||
...
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1324,6 +1367,8 @@ You can ensure that the struct is always passed by reference by
|
|||
adding `&`:
|
||||
|
||||
```v
|
||||
struct Foo{ abc int }
|
||||
|
||||
fn (foo &Foo) bar() {
|
||||
println(foo.abc)
|
||||
}
|
||||
|
@ -1333,9 +1378,9 @@ fn (foo &Foo) bar() {
|
|||
`(mut foo Foo)` must be used.
|
||||
|
||||
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> {
|
||||
val T
|
||||
left &Node
|
||||
|
@ -1394,9 +1439,7 @@ They can represent complex structures, and this is used quite often since there
|
|||
are no globals:
|
||||
-->
|
||||
|
||||
```v
|
||||
println('Top cities: $TOP_CITIES.filter(.usa)')
|
||||
vs
|
||||
```v ignore
|
||||
println('Top cities: $top_cities.filter(.usa)')
|
||||
```
|
||||
|
||||
|
@ -1406,6 +1449,7 @@ println('Top cities: $top_cities.filter(.usa)')
|
|||
strings, numbers, arrays, maps, structs.
|
||||
|
||||
```v
|
||||
struct User{ name string age int }
|
||||
println(1) // "1"
|
||||
println('hi') // "hi"
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
```v ignore
|
||||
fn exit(exit_code int)
|
||||
fn panic(message string)
|
||||
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
|
||||
.v files with code:
|
||||
|
||||
```
|
||||
```shell
|
||||
cd ~/code/modules
|
||||
mkdir mymodule
|
||||
vim mymodule/myfile.v
|
||||
```
|
||||
```v
|
||||
```v failcompile
|
||||
// myfile.v
|
||||
module mymodule
|
||||
|
||||
|
@ -1466,7 +1510,7 @@ pub fn say_hi() {
|
|||
|
||||
You can now use `mymodule` in your code:
|
||||
|
||||
```v
|
||||
```v failcompile
|
||||
import mymodule
|
||||
|
||||
fn main() {
|
||||
|
@ -1574,14 +1618,19 @@ struct Venus {}
|
|||
type World = Moon | Mars | Venus
|
||||
|
||||
sum := World(Moon{})
|
||||
println(sum)
|
||||
```
|
||||
|
||||
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`:
|
||||
|
||||
```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() {
|
||||
mut w := World(Moon{})
|
||||
assert w is Moon
|
||||
|
@ -1600,8 +1649,11 @@ fn main() {
|
|||
You can also use `match` to determine the variant:
|
||||
|
||||
```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) {
|
||||
match w {
|
||||
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
|
||||
|
||||
```v
|
||||
fn (m Moon) moon_walk()
|
||||
fn (m Mars) shiver()
|
||||
fn (v Venus) sweat()
|
||||
struct Moon {}
|
||||
struct Mars {}
|
||||
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) {
|
||||
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
|
||||
to the `error()` function. `err` is empty if `none` was returned.
|
||||
|
||||
```v
|
||||
```v oksyntax
|
||||
user := repo.find_user_by_id(7) or {
|
||||
println(err) // "User 7 not found"
|
||||
return
|
||||
|
@ -1730,7 +1788,7 @@ any further.
|
|||
|
||||
The body of `f` is essentially a condensed version of:
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
resp := http.get(url) or {
|
||||
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:
|
||||
|
||||
```v
|
||||
```v oksyntax
|
||||
user := repo.find_user_by_id(7) or {
|
||||
return
|
||||
}
|
||||
|
@ -1767,13 +1825,16 @@ fn do_something(s string) ?string {
|
|||
|
||||
a := do_something('foo') or { 'default' } // a will be 'foo'
|
||||
b := do_something('bar') or { 'default' } // b will be 'default'
|
||||
println(a)
|
||||
println(b)
|
||||
```
|
||||
|
||||
---
|
||||
The fourth method is to use `if` unwrapping:
|
||||
|
||||
```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
|
||||
} else {
|
||||
println(err)
|
||||
|
@ -1784,7 +1845,8 @@ Above, `http.get` returns a `?http.Response`. `resp` is only in scope for the fi
|
|||
|
||||
## Generics
|
||||
|
||||
```v
|
||||
```v wip
|
||||
|
||||
struct Repo<T> {
|
||||
db DB
|
||||
}
|
||||
|
@ -1897,13 +1959,13 @@ variables:
|
|||
|
||||
```v
|
||||
fn f(ch chan int) {
|
||||
...
|
||||
// ...
|
||||
}
|
||||
|
||||
fn main() {
|
||||
...
|
||||
ch := chan int{}
|
||||
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:
|
||||
|
||||
```v
|
||||
mut ch := chan int{}
|
||||
mut ch2 := chan f64{}
|
||||
n := 5
|
||||
x := 7.3
|
||||
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
|
||||
handled using an or branch (see [Handling Optionals](#handling-optionals)).
|
||||
|
||||
```v
|
||||
```v wip
|
||||
mut ch := chan int{}
|
||||
mut ch2 := chan f64{}
|
||||
// ...
|
||||
ch.close()
|
||||
...
|
||||
// ...
|
||||
m := <-ch or {
|
||||
println('channel has been closed')
|
||||
}
|
||||
|
@ -1943,7 +2010,15 @@ y := <-ch2 ?
|
|||
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
|
||||
of statements - similar to the [match](#match) command:
|
||||
```v
|
||||
```v wip
|
||||
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 {
|
||||
// do something with `a`
|
||||
|
@ -1958,6 +2033,7 @@ select {
|
|||
// 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.
|
||||
|
@ -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`
|
||||
that becomes `false` if all channels are closed:
|
||||
```v
|
||||
```v wip
|
||||
if select {
|
||||
ch <- a {
|
||||
...
|
||||
// ...
|
||||
}
|
||||
} {
|
||||
// channel was open
|
||||
|
@ -1982,10 +2058,21 @@ if select {
|
|||
|
||||
For special purposes there are some builtin properties and methods:
|
||||
```v
|
||||
struct Abc{x int}
|
||||
|
||||
a := 2.13
|
||||
mut ch := chan f64{}
|
||||
|
||||
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
|
||||
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
|
||||
`.success`, `.not_ready` or `.closed` - dependent on whether the object has been transferred or
|
||||
|
@ -2007,12 +2094,12 @@ mut:
|
|||
}
|
||||
|
||||
fn (mut b St) g() {
|
||||
...
|
||||
|
||||
b.mtx.m_lock()
|
||||
// read/modify/write b.x
|
||||
...
|
||||
|
||||
b.mtx.unlock()
|
||||
...
|
||||
|
||||
}
|
||||
|
||||
fn caller() {
|
||||
|
@ -2021,12 +2108,12 @@ fn caller() {
|
|||
mtx: sync.new_mutex()
|
||||
}
|
||||
go a.g()
|
||||
...
|
||||
|
||||
a.mtx.m_lock()
|
||||
// read/modify/write a.x
|
||||
...
|
||||
|
||||
a.mtx.unlock()
|
||||
...
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -2079,9 +2166,10 @@ No runtime reflection is used. This results in much better performance.
|
|||
### Asserts
|
||||
|
||||
```v
|
||||
mut v := 2
|
||||
fn foo(mut v []int) { v[0] = 1 }
|
||||
mut v := [20]
|
||||
foo(mut v)
|
||||
assert v < 4
|
||||
assert v[0] < 4
|
||||
```
|
||||
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
|
||||
|
@ -2101,7 +2189,7 @@ fn main() {
|
|||
println(hello())
|
||||
}
|
||||
```
|
||||
```v
|
||||
```v failcompile
|
||||
module main
|
||||
// hello_test.v
|
||||
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:
|
||||
|
||||
```v
|
||||
fn draw_text(s string, x, y int) {
|
||||
...
|
||||
import strings
|
||||
fn draw_text(s string, x int, y int) {
|
||||
// ...
|
||||
}
|
||||
|
||||
fn draw_scene() {
|
||||
...
|
||||
// ...
|
||||
name1 := 'abc'
|
||||
name2 := 'def ghi'
|
||||
draw_text('hello $name1', 10, 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
|
||||
struct User{ name string }
|
||||
fn test() []int {
|
||||
number := 7 // stack variable
|
||||
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.)
|
||||
|
||||
```v
|
||||
import sqlite
|
||||
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
|
||||
name string
|
||||
|
@ -2207,7 +2300,7 @@ struct Customer { // struct name has to be the same as the table name (for now)
|
|||
country string
|
||||
}
|
||||
|
||||
db := sqlite.connect('customers.db')
|
||||
db := sqlite.connect('customers.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.
|
||||
`v fmt` takes care of that:
|
||||
|
||||
```v
|
||||
```shell
|
||||
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:
|
||||
|
||||
```v
|
||||
```v failcompile
|
||||
// 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
|
||||
unsafe {
|
||||
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)
|
||||
|
||||
```v
|
||||
```v oksyntax
|
||||
#flag linux -lsdl2
|
||||
#flag linux -Ivig
|
||||
#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:
|
||||
|
||||
```v
|
||||
```v oksyntax
|
||||
#pkgconfig 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
|
||||
created your module with `v new` you already have v.mod file). For
|
||||
example:
|
||||
```v
|
||||
```v ignore
|
||||
Module {
|
||||
name: 'mymodule',
|
||||
description: 'My nice module wraps a simple C library.',
|
||||
|
@ -2456,7 +2549,7 @@ Module {
|
|||
|
||||
|
||||
* Add these lines to the top of your module:
|
||||
```v
|
||||
```v oksyntax
|
||||
#flag -I @VROOT/c
|
||||
#flag @VROOT/c/implementation.o
|
||||
#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:
|
||||
```v
|
||||
```v ignore
|
||||
import v.vmod
|
||||
vm := vmod.decode( @VMOD_FILE ) or { panic(err) }
|
||||
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
|
||||
serializers for any data format. V has compile-time `if` and `for` constructs:
|
||||
|
||||
```v
|
||||
```v wip
|
||||
// TODO: not fully implemented
|
||||
|
||||
struct User {
|
||||
|
@ -2732,7 +2825,7 @@ To improve safety and maintainability, operator overloading is limited:
|
|||
|
||||
TODO: not implemented yet
|
||||
|
||||
```v
|
||||
```v failcompile
|
||||
fn main() {
|
||||
a := 10
|
||||
asm x64 {
|
||||
|
@ -2767,7 +2860,7 @@ int main() {
|
|||
Run `v translate test.cpp` and V will generate `test.v`:
|
||||
|
||||
```v
|
||||
fn main {
|
||||
fn main() {
|
||||
mut s := []string{}
|
||||
s << 'V is '
|
||||
s << 'awesome'
|
||||
|
@ -2825,13 +2918,13 @@ More examples, including a graphical application:
|
|||
|
||||
To cross compile your project simply run
|
||||
|
||||
```v
|
||||
```shell
|
||||
v -os windows .
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```v
|
||||
```shell
|
||||
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`
|
||||
module global (so that you can use `ls()` instead of `os.ls()`, for example).
|
||||
|
||||
```v
|
||||
```v wip
|
||||
#!/usr/local/bin/v run
|
||||
// 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
|
||||
|
@ -2930,7 +3023,7 @@ fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)
|
|||
|
||||
V has 41 reserved keywords (3 are literals):
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
as
|
||||
asm
|
||||
assert
|
||||
|
@ -2978,7 +3071,7 @@ See also [Types](#types).
|
|||
|
||||
This lists operators for [primitive types](#primitive-types) only.
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
+ sum integers, floats, strings
|
||||
- difference integers, floats
|
||||
* product integers, floats
|
||||
|
|
|
@ -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
|
||||
4 kinds of declaration has to be chosen:
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
a := ...
|
||||
mut b := ...
|
||||
shared c := ...
|
||||
|
@ -41,14 +41,14 @@ atomic d := ...
|
|||
*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
|
||||
other coroutines. This is done by one the following block structures:
|
||||
```v
|
||||
```v ignore
|
||||
lock c {
|
||||
// read, modify, write c
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
rlock 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
|
||||
used to call functions expecting immutable arguments:
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
fn f(x St) {...}
|
||||
fn g(mut 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 an `rlock c {...}` block like an immutable:
|
||||
```v
|
||||
```v ignore
|
||||
shared c := &St{...}
|
||||
lock 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
|
||||
can be generated automatically for `array`/`map` operations:
|
||||
|
||||
```v
|
||||
```v ignore
|
||||
shared a []int{...}
|
||||
go h2(shared a)
|
||||
a << 3
|
||||
|
|
Loading…
Reference in New Issue