mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
Compare commits
37 Commits
2d838d5178
...
a9a94cfd51
Author | SHA1 | Date | |
---|---|---|---|
|
a9a94cfd51 | ||
|
aef4367a27 | ||
|
413ffbfc3b | ||
|
029e8a815b | ||
|
efcb15d05b | ||
|
52a055b6bc | ||
|
c422919481 | ||
|
1c7df29bed | ||
|
a43064af07 | ||
|
045adb6600 | ||
|
87dd5de191 | ||
|
72cd9b80a3 | ||
|
6b792b1257 | ||
|
6b29d628c3 | ||
|
6a8a22891d | ||
|
acd581add5 | ||
|
6b00685629 | ||
|
e7af25ec14 | ||
|
f8e89ae91c | ||
|
c9e8dd56c2 | ||
|
1728e4c73e | ||
|
921a2e1c2e | ||
|
0498f4c40f | ||
|
59eb76c81d | ||
|
8f3a1751e3 | ||
|
5355c67ebe | ||
|
4f518c2850 | ||
|
54635185c4 | ||
|
17b576227f | ||
|
b3a6b73306 | ||
|
97a726b188 | ||
|
7fe794a974 | ||
|
e7e5a07aa2 | ||
|
7d6e15fa66 | ||
|
ded6c38061 | ||
|
de392003be | ||
|
11f06e41c0 |
|
@ -85,18 +85,17 @@ jobs:
|
||||||
# v run /tmp/gitly/tests/first_run.v
|
# v run /tmp/gitly/tests/first_run.v
|
||||||
# # /tmp/gitly/gitly -ci_run
|
# # /tmp/gitly/gitly -ci_run
|
||||||
|
|
||||||
# - name: Build V Language Server (VLS) vlang/vls
|
- name: Build V Language Server (v-analyzer) v-analyzer/v-analyzer
|
||||||
# run: |
|
run: |
|
||||||
# echo "Clone VLS"
|
echo "Clone v-analyzer"
|
||||||
# git clone --depth 1 https://github.com/vlang/vls /tmp/vls
|
git clone --depth 1 https://github.com/v-analyzer/v-analyzer /tmp/v-analyzer
|
||||||
# echo "Build VLS"
|
cd /tmp/v-analyzer
|
||||||
# v /tmp/vls/cmd/vls
|
echo "Installing dependencies"
|
||||||
# echo "Build VLS with -prod"
|
v install
|
||||||
# v -prod /tmp/vls/cmd/vls
|
echo "Build v-analyzer debug"
|
||||||
# echo "Build VLS with -gc boehm -skip-unused"
|
v build.vsh debug
|
||||||
# v -gc boehm -skip-unused /tmp/vls/cmd/vls
|
echo "Build v-analyzer release"
|
||||||
# echo "Test VLS with gcc"
|
v build.vsh release
|
||||||
# v -cc gcc test /tmp/vls
|
|
||||||
|
|
||||||
- name: Build vlang/go2v
|
- name: Build vlang/go2v
|
||||||
run: |
|
run: |
|
||||||
|
|
58
.github/workflows/wasm_backend_tests_ci.yml
vendored
58
.github/workflows/wasm_backend_tests_ci.yml
vendored
|
@ -60,8 +60,10 @@ jobs:
|
||||||
- name: Build V
|
- name: Build V
|
||||||
run: make -j4 && ./v symlink -githubci
|
run: make -j4 && ./v symlink -githubci
|
||||||
|
|
||||||
- name: Install binaryen as build dependency for the V WASM backend
|
- name: Install wasmer to execute WASM modules
|
||||||
run: ./v cmd/tools/install_binaryen.vsh
|
run: |
|
||||||
|
curl https://get.wasmer.io -sSfL | sh
|
||||||
|
sudo ln -s ~/.wasmer/bin/wasmer /usr/local/bin
|
||||||
|
|
||||||
- name: Build the V WASM backend
|
- name: Build the V WASM backend
|
||||||
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
||||||
|
@ -82,8 +84,10 @@ jobs:
|
||||||
- name: Build V
|
- name: Build V
|
||||||
run: make -j4 && ./v symlink -githubci
|
run: make -j4 && ./v symlink -githubci
|
||||||
|
|
||||||
- name: Install binaryen as build dependency for the V WASM backend
|
- name: Install wasmer to execute WASM modules
|
||||||
run: ./v cmd/tools/install_binaryen.vsh
|
run: |
|
||||||
|
curl https://get.wasmer.io -sSfL | sh
|
||||||
|
sudo ln -s ~/.wasmer/bin/wasmer /usr/local/bin
|
||||||
|
|
||||||
- name: Build the V WASM backend
|
- name: Build the V WASM backend
|
||||||
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
||||||
|
@ -94,26 +98,26 @@ jobs:
|
||||||
- name: Build examples
|
- name: Build examples
|
||||||
run: VTEST_ONLY=wasm ./v build-examples
|
run: VTEST_ONLY=wasm ./v build-examples
|
||||||
|
|
||||||
wasm-backend-windows:
|
# wasm-backend-windows:
|
||||||
runs-on: windows-2022
|
# runs-on: windows-2022
|
||||||
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
# if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
||||||
timeout-minutes: 121
|
# timeout-minutes: 121
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v3
|
# - uses: actions/checkout@v3
|
||||||
|
#
|
||||||
- name: Build V
|
# - name: Build V
|
||||||
run: .\make.bat -msvc
|
# run: .\make.bat -msvc
|
||||||
- name: Symlink V
|
# - name: Symlink V
|
||||||
run: .\v.exe symlink -githubci
|
# run: .\v.exe symlink -githubci
|
||||||
|
#
|
||||||
- name: Install binaryen as build dependency for the V WASM backend
|
# - name: Install binaryen as build dependency for the V WASM backend
|
||||||
run: v cmd/tools/install_binaryen.vsh
|
# run: v cmd/tools/install_binaryen.vsh
|
||||||
|
#
|
||||||
- name: Build the V WASM backend
|
# - name: Build the V WASM backend
|
||||||
run: v -cc msvc -showcc -v cmd/tools/builders/wasm_builder.v
|
# run: v -cc msvc -showcc -v cmd/tools/builders/wasm_builder.v
|
||||||
|
#
|
||||||
- name: Test the WASM backend
|
# - name: Test the WASM backend
|
||||||
run: v -stats test vlib/v/gen/wasm/tests/
|
# run: v -stats test vlib/v/gen/wasm/tests/
|
||||||
|
#
|
||||||
- name: Build examples
|
# - name: Build examples
|
||||||
run: $env:VTEST_ONLY='wasm'; v build-examples
|
# run: $env:VTEST_ONLY='wasm'; v build-examples
|
||||||
|
|
73
.github/workflows/wasm_wabt_tests_ci.yml
vendored
73
.github/workflows/wasm_wabt_tests_ci.yml
vendored
|
@ -1,73 +0,0 @@
|
||||||
name: wasm wabt validate tests CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '!**'
|
|
||||||
- '!**.md'
|
|
||||||
- 'vlib/builtin/**.v'
|
|
||||||
- 'vlib/wasm/**.v'
|
|
||||||
- 'vlib/wasm/tests/**.v'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '!**'
|
|
||||||
- '!**.md'
|
|
||||||
- 'vlib/builtin/**.v'
|
|
||||||
- 'vlib/wasm/**.v'
|
|
||||||
- 'vlib/wasm/tests/**.v'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: wasm-wabt-ci-${{ github.event.pull_request.number || github.sha }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
wasm-wabt-ubuntu:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
|
||||||
timeout-minutes: 31
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Build V
|
|
||||||
run: make && ./v symlink -githubci
|
|
||||||
|
|
||||||
- name: Install wabt to get the wasm-validate executable
|
|
||||||
run: v cmd/tools/install_wabt.vsh
|
|
||||||
|
|
||||||
- name: Test the WASM backend
|
|
||||||
run: v test vlib/wasm/
|
|
||||||
|
|
||||||
wasm-wabt-macos:
|
|
||||||
runs-on: macOS-12
|
|
||||||
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
|
||||||
timeout-minutes: 31
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Build V
|
|
||||||
run: make && ./v symlink -githubci
|
|
||||||
|
|
||||||
- name: Install wabt to get the wasm-validate executable
|
|
||||||
run: v cmd/tools/install_wabt.vsh
|
|
||||||
|
|
||||||
- name: Test the WASM backend
|
|
||||||
run: v test vlib/wasm/
|
|
||||||
|
|
||||||
wasm-wabt-windows:
|
|
||||||
runs-on: windows-2022
|
|
||||||
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
|
|
||||||
timeout-minutes: 31
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Build V
|
|
||||||
run: .\make.bat -msvc
|
|
||||||
|
|
||||||
- name: Symlink V
|
|
||||||
run: .\v.exe symlink -githubci
|
|
||||||
|
|
||||||
- name: Install wabt to get the wasm-validate executable
|
|
||||||
run: v cmd/tools/install_wabt.vsh
|
|
||||||
|
|
||||||
- name: Test the WASM backend
|
|
||||||
run: v test vlib/wasm/
|
|
|
@ -24,8 +24,6 @@ const (
|
||||||
'crypto.rand',
|
'crypto.rand',
|
||||||
'os.bare',
|
'os.bare',
|
||||||
'os2',
|
'os2',
|
||||||
'picohttpparser',
|
|
||||||
'picoev',
|
|
||||||
'szip',
|
'szip',
|
||||||
'v.eval',
|
'v.eval',
|
||||||
]
|
]
|
||||||
|
|
0
cmd/tools/install_wabt.vsh
Normal file → Executable file
0
cmd/tools/install_wabt.vsh
Normal file → Executable file
|
@ -625,9 +625,8 @@ pub fn prepare_test_session(zargs string, folder string, oskipped []string, main
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
$if windows {
|
$if windows {
|
||||||
// skip pico and process/command examples on windows
|
// skip process/command examples on windows
|
||||||
if fnormalised.ends_with('examples/pico/pico.v')
|
if fnormalised.ends_with('examples/process/command.v') {
|
||||||
|| fnormalised.ends_with('examples/process/command.v') {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
cmd/v/v.v
11
cmd/v/v.v
|
@ -193,18 +193,7 @@ fn rebuild(prefs &pref.Preferences) {
|
||||||
util.launch_tool(prefs.is_verbose, 'builders/golang_builder', os.args[1..])
|
util.launch_tool(prefs.is_verbose, 'builders/golang_builder', os.args[1..])
|
||||||
}
|
}
|
||||||
.wasm {
|
.wasm {
|
||||||
assert_wasm_backend_thirdparty()
|
|
||||||
util.launch_tool(prefs.is_verbose, 'builders/wasm_builder', os.args[1..])
|
util.launch_tool(prefs.is_verbose, 'builders/wasm_builder', os.args[1..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_wasm_backend_thirdparty() {
|
|
||||||
vroot := os.dir(pref.vexe_path())
|
|
||||||
if !os.exists('${vroot}/cmd/tools/builders/wasm_builder')
|
|
||||||
&& !os.exists('${vroot}/thirdparty/binaryen') {
|
|
||||||
eprintln('The WebAssembly backend requires `binaryen`, an external library dependency')
|
|
||||||
eprintln('This can be installed with `./cmd/tools/install_binaryen.vsh`, to download prebuilt libraries for your platform')
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
12
doc/docs.md
12
doc/docs.md
|
@ -4551,9 +4551,9 @@ If a test function has an error return type, any propagated errors will fail the
|
||||||
```v
|
```v
|
||||||
import strconv
|
import strconv
|
||||||
|
|
||||||
fn test_atoi() ? {
|
fn test_atoi() ! {
|
||||||
assert strconv.atoi('1')? == 1
|
assert strconv.atoi('1')! == 1
|
||||||
assert strconv.atoi('one')? == 1 // test will fail
|
assert strconv.atoi('one')! == 1 // test will fail
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -6664,7 +6664,7 @@ println('c: ${c}') // 120
|
||||||
```
|
```
|
||||||
|
|
||||||
For more examples, see
|
For more examples, see
|
||||||
[github.com/vlang/v/tree/master/vlib/v/tests/assembly/asm_test.amd64.v](https://github.com/vlang/v/tree/master/vlib/v/tests/assembly/asm_test.amd64.v)
|
[vlib/v/slow_tests/assembly/asm_test.amd64.v](https://github.com/vlang/v/tree/master/vlib/v/slow_tests/assembly/asm_test.amd64.v)
|
||||||
|
|
||||||
### Hot code reloading
|
### Hot code reloading
|
||||||
|
|
||||||
|
@ -6738,7 +6738,7 @@ fn sh(cmd string) {
|
||||||
rmdir_all('build') or {}
|
rmdir_all('build') or {}
|
||||||
|
|
||||||
// Create build/, never fails as build/ does not exist
|
// Create build/, never fails as build/ does not exist
|
||||||
mkdir('build')?
|
mkdir('build')!
|
||||||
|
|
||||||
// Move *.v files to build/
|
// Move *.v files to build/
|
||||||
result := execute('mv *.v build/')
|
result := execute('mv *.v build/')
|
||||||
|
@ -6749,7 +6749,7 @@ if result.exit_code != 0 {
|
||||||
sh('ls')
|
sh('ls')
|
||||||
|
|
||||||
// Similar to:
|
// Similar to:
|
||||||
// files := ls('.')?
|
// files := ls('.')!
|
||||||
// mut count := 0
|
// mut count := 0
|
||||||
// if files.len > 0 {
|
// if files.len > 0 {
|
||||||
// for file in files {
|
// for file in files {
|
||||||
|
|
|
@ -3,7 +3,7 @@ module some_module
|
||||||
import eventbus
|
import eventbus
|
||||||
|
|
||||||
const (
|
const (
|
||||||
eb = eventbus.new()
|
eb = eventbus.new[string]()
|
||||||
)
|
)
|
||||||
|
|
||||||
pub struct Duration {
|
pub struct Duration {
|
||||||
|
@ -29,6 +29,6 @@ pub fn do_work() {
|
||||||
some_module.eb.publish('event_baz', &Duration{42}, &EventMetadata{'Additional data at the end.'})
|
some_module.eb.publish('event_baz', &Duration{42}, &EventMetadata{'Additional data at the end.'})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_subscriber() eventbus.Subscriber {
|
pub fn get_subscriber() eventbus.Subscriber[string] {
|
||||||
return *some_module.eb.subscriber
|
return *some_module.eb.subscriber
|
||||||
}
|
}
|
||||||
|
|
56
examples/gg/many_thousands_of_circles.v
Normal file
56
examples/gg/many_thousands_of_circles.v
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
module main
|
||||||
|
|
||||||
|
import gg
|
||||||
|
import gx
|
||||||
|
import rand
|
||||||
|
|
||||||
|
const max_circles_per_pass = 1000
|
||||||
|
|
||||||
|
// prepare some random colors ahead of time
|
||||||
|
const colors = []gx.Color{len: max_circles_per_pass, init: gx.Color{
|
||||||
|
r: u8(index * 0 + rand.u8())
|
||||||
|
g: u8(index * 0 + rand.u8())
|
||||||
|
b: u8(index * 0 + rand.u8())
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn frame(mut ctx gg.Context) {
|
||||||
|
// First pass, just clears the background:
|
||||||
|
ctx.begin()
|
||||||
|
ctx.end()
|
||||||
|
|
||||||
|
// We want to draw thousands of circles, but sokol has a limit for how
|
||||||
|
// many primitives can be in a single pass, and if you reach that limit
|
||||||
|
// you will not see *anything at all* for that pass.
|
||||||
|
// For the circles below, that limit is ~2520 circles per pass.
|
||||||
|
// To overcome this, we will use several passes instead, where each one
|
||||||
|
// will draw just 1000 circles.
|
||||||
|
// In other words, in total we will have 4 * 1000 = 4000 circles, drawn with
|
||||||
|
// 4 passes.
|
||||||
|
for i := 0; i < 4 * max_circles_per_pass; i += max_circles_per_pass {
|
||||||
|
ctx.begin()
|
||||||
|
for c in 0 .. max_circles_per_pass {
|
||||||
|
rx := rand.int_in_range(0, ctx.window.width) or { 0 }
|
||||||
|
ry := rand.int_in_range(0, ctx.window.height) or { 0 }
|
||||||
|
ctx.draw_circle_filled(rx, ry, 10, colors[c])
|
||||||
|
}
|
||||||
|
ctx.end(how: .passthru)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last pass, is for the fps overlay, that should be *always on top of everything*.
|
||||||
|
// Drawing it in a separate pass, guarantees, that it *will* be drawn, even if the drawing
|
||||||
|
// of all the other passes fail. Try increasing max_circles_per_pass to 3000 for example.
|
||||||
|
ctx.begin()
|
||||||
|
ctx.show_fps()
|
||||||
|
ctx.end(how: .passthru)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut ctx := gg.new_context(
|
||||||
|
window_title: 'Many Thousands of Circles'
|
||||||
|
bg_color: gx.black
|
||||||
|
width: 600
|
||||||
|
height: 400
|
||||||
|
frame_fn: frame
|
||||||
|
)
|
||||||
|
ctx.run()
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import picoev
|
||||||
import picohttpparser
|
import picohttpparser
|
||||||
|
|
||||||
const (
|
const (
|
||||||
port = 8088
|
port = 8089
|
||||||
)
|
)
|
||||||
|
|
||||||
struct Message {
|
struct Message {
|
||||||
|
@ -24,21 +24,25 @@ fn hello_response() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response) {
|
fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response) {
|
||||||
if picohttpparser.cmpn(req.method, 'GET ', 4) {
|
if req.method == 'GET' {
|
||||||
if picohttpparser.cmp(req.path, '/t') {
|
if req.path == '/t' {
|
||||||
res.http_ok()
|
res.http_ok()
|
||||||
res.header_server()
|
res.header_server()
|
||||||
res.header_date()
|
res.header_date()
|
||||||
res.plain()
|
res.plain()
|
||||||
res.body(hello_response())
|
res.body(hello_response())
|
||||||
} else if picohttpparser.cmp(req.path, '/j') {
|
} else if req.path == '/j' {
|
||||||
res.http_ok()
|
res.http_ok()
|
||||||
res.header_server()
|
res.header_server()
|
||||||
res.header_date()
|
res.header_date()
|
||||||
res.json()
|
res.json()
|
||||||
res.body(json_response())
|
res.body(json_response())
|
||||||
} else {
|
} else {
|
||||||
res.http_404()
|
res.http_ok()
|
||||||
|
res.header_server()
|
||||||
|
res.header_date()
|
||||||
|
res.html()
|
||||||
|
res.body('Hello Picoev!\n')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.http_405()
|
res.http_405()
|
||||||
|
@ -48,5 +52,6 @@ fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Res
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println('Starting webserver on http://127.0.0.1:${port}/ ...')
|
println('Starting webserver on http://127.0.0.1:${port}/ ...')
|
||||||
picoev.new(port: port, cb: &callback).serve()
|
mut server := picoev.new(port: port, cb: callback)
|
||||||
|
server.serve()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
module arrays
|
module arrays
|
||||||
|
|
||||||
|
import strings
|
||||||
|
|
||||||
// Common arrays functions:
|
// Common arrays functions:
|
||||||
// - min / max - return the value of the minumum / maximum
|
// - min / max - return the value of the minumum / maximum
|
||||||
// - idx_min / idx_max - return the index of the first minumum / maximum
|
// - idx_min / idx_max - return the index of the first minumum / maximum
|
||||||
|
@ -664,3 +666,53 @@ pub fn carray_to_varray[T](c_array_data voidptr, items int) []T {
|
||||||
unsafe { vmemcpy(v_array.data, c_array_data, total_size) }
|
unsafe { vmemcpy(v_array.data, c_array_data, total_size) }
|
||||||
return v_array
|
return v_array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find_first returns the first element that matches the given predicate
|
||||||
|
// returns `none`, if there is no match found
|
||||||
|
// Example: arrays.find_first([1, 2, 3, 4, 5], fn (arr int) bool { arr == 3}) // => 3
|
||||||
|
pub fn find_first[T](array []T, predicate fn (elem T) bool) ?T {
|
||||||
|
if array.len == 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
for item in array {
|
||||||
|
if predicate(item) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// find_last returns the last element that matches the given predicate
|
||||||
|
// returns `none`, if there is no match found
|
||||||
|
// Example: arrays.find_last([1, 2, 3, 4, 5], fn (arr int) bool { arr == 3}) // => 3
|
||||||
|
pub fn find_last[T](array []T, predicate fn (elem T) bool) ?T {
|
||||||
|
if array.len == 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
for idx := array.len - 1; idx >= 0; idx-- {
|
||||||
|
item := array[idx]
|
||||||
|
if predicate(item) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// join_to_string takes in a custom transform function and joins all elements into a string with
|
||||||
|
// the specified separator
|
||||||
|
[manualfree]
|
||||||
|
pub fn join_to_string[T](array []T, separator string, transform fn (elem T) string) string {
|
||||||
|
mut sb := strings.new_builder(array.len * 2)
|
||||||
|
defer {
|
||||||
|
unsafe { sb.free() }
|
||||||
|
}
|
||||||
|
for i, item in array {
|
||||||
|
x := transform(item)
|
||||||
|
sb.write_string(x)
|
||||||
|
unsafe { x.free() }
|
||||||
|
if i < array.len - 1 {
|
||||||
|
sb.write_string(separator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.str()
|
||||||
|
}
|
||||||
|
|
|
@ -404,3 +404,71 @@ fn test_map_of_counts() {
|
||||||
assert map_of_counts(['abc', 'def', 'abc']) == {'abc': 2, 'def': 1}
|
assert map_of_counts(['abc', 'def', 'abc']) == {'abc': 2, 'def': 1}
|
||||||
// vfmt on
|
// vfmt on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FindTest {
|
||||||
|
name string
|
||||||
|
age int
|
||||||
|
}
|
||||||
|
|
||||||
|
const test_structs = [FindTest{'one', 1}, FindTest{'two', 2},
|
||||||
|
FindTest{'three', 3}, FindTest{'one', 4}]
|
||||||
|
|
||||||
|
fn test_find_first() {
|
||||||
|
// element in array
|
||||||
|
a := [1, 2, 3, 4, 5]
|
||||||
|
assert find_first[int](a, fn (arr int) bool {
|
||||||
|
return arr == 3
|
||||||
|
})? == 3, 'find element couldnt find the right element'
|
||||||
|
|
||||||
|
// find struct
|
||||||
|
find_by_name := find_first(arrays.test_structs, fn (arr FindTest) bool {
|
||||||
|
return arr.name == 'one'
|
||||||
|
})?
|
||||||
|
assert find_by_name == FindTest{'one', 1}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
if _ := find_first(arrays.test_structs, fn (arr FindTest) bool {
|
||||||
|
return arr.name == 'nothing'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
assert false
|
||||||
|
} else {
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_find_last() {
|
||||||
|
// // element in array
|
||||||
|
a := [1, 2, 3, 4, 5]
|
||||||
|
assert find_last[int](a, fn (arr int) bool {
|
||||||
|
return arr == 3
|
||||||
|
})? == 3, 'find element couldnt find the right element'
|
||||||
|
|
||||||
|
// find struct
|
||||||
|
find_by_name := find_last(arrays.test_structs, fn (arr FindTest) bool {
|
||||||
|
return arr.name == 'one'
|
||||||
|
})?
|
||||||
|
assert find_by_name == FindTest{'one', 4}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
if _ := find_last(arrays.test_structs, fn (arr FindTest) bool {
|
||||||
|
return arr.name == 'nothing'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
assert false
|
||||||
|
} else {
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_join_to_string() {
|
||||||
|
assert join_to_string[FindTest](arrays.test_structs, ':', fn (it FindTest) string {
|
||||||
|
return it.name
|
||||||
|
}) == 'one:two:three:one'
|
||||||
|
assert join_to_string[FindTest](arrays.test_structs, '', fn (it FindTest) string {
|
||||||
|
return it.name
|
||||||
|
}) == 'onetwothreeone'
|
||||||
|
assert join_to_string[int]([]int{}, ':', fn (it int) string {
|
||||||
|
return '1'
|
||||||
|
}) == ''
|
||||||
|
}
|
||||||
|
|
|
@ -295,7 +295,7 @@ fn C.SymCleanup(hProcess voidptr)
|
||||||
|
|
||||||
fn C.MultiByteToWideChar(codePage u32, dwFlags u32, lpMultiMyteStr &char, cbMultiByte int, lpWideCharStr &u16, cchWideChar int) int
|
fn C.MultiByteToWideChar(codePage u32, dwFlags u32, lpMultiMyteStr &char, cbMultiByte int, lpWideCharStr &u16, cchWideChar int) int
|
||||||
|
|
||||||
fn C.wcslen(str &u16) int
|
fn C.wcslen(str voidptr) usize
|
||||||
|
|
||||||
fn C.WideCharToMultiByte(codePage u32, dwFlags u32, lpWideCharStr &u16, cchWideChar int, lpMultiByteStr &char, cbMultiByte int, lpDefaultChar &char, lpUsedDefaultChar &int) int
|
fn C.WideCharToMultiByte(codePage u32, dwFlags u32, lpWideCharStr &u16, cchWideChar int, lpMultiByteStr &char, cbMultiByte int, lpDefaultChar &char, lpUsedDefaultChar &int) int
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ const cp_utf8 = 65001
|
||||||
// The returned pointer of .to_wide(), has a type of &u16, and is suitable
|
// The returned pointer of .to_wide(), has a type of &u16, and is suitable
|
||||||
// for passing to Windows APIs that expect LPWSTR or wchar_t* parameters.
|
// for passing to Windows APIs that expect LPWSTR or wchar_t* parameters.
|
||||||
// See also MultiByteToWideChar ( https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar )
|
// See also MultiByteToWideChar ( https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar )
|
||||||
|
// See also builtin.wchar.from_string/1, for a version, that produces a
|
||||||
|
// platform dependant L"" C style wchar_t* wide string.
|
||||||
pub fn (_str string) to_wide() &u16 {
|
pub fn (_str string) to_wide() &u16 {
|
||||||
$if windows {
|
$if windows {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -29,19 +31,25 @@ pub fn (_str string) to_wide() &u16 {
|
||||||
for i, r in srunes {
|
for i, r in srunes {
|
||||||
result[i] = u16(r)
|
result[i] = u16(r)
|
||||||
}
|
}
|
||||||
|
result[srunes.len] = 0
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// string_from_wide creates a V string, encoded in UTF-8, given a windows
|
// string_from_wide creates a V string, encoded in UTF-8, given a windows
|
||||||
// style string encoded in UTF-16.
|
// style string encoded in UTF-16. Note that this function first searches
|
||||||
|
// for the string terminator 0 character, and is thus slower, while more
|
||||||
|
// convenient compared to string_from_wide2/2 (you have to know the length
|
||||||
|
// in advance to use string_from_wide2/2).
|
||||||
|
// See also builtin.wchar.to_string/1, for a version that eases working with
|
||||||
|
// the platform dependent &wchar_t L"" strings.
|
||||||
[manualfree; unsafe]
|
[manualfree; unsafe]
|
||||||
pub fn string_from_wide(_wstr &u16) string {
|
pub fn string_from_wide(_wstr &u16) string {
|
||||||
$if windows {
|
$if windows {
|
||||||
unsafe {
|
unsafe {
|
||||||
wstr_len := C.wcslen(_wstr)
|
wstr_len := C.wcslen(_wstr)
|
||||||
return string_from_wide2(_wstr, wstr_len)
|
return string_from_wide2(_wstr, int(wstr_len))
|
||||||
}
|
}
|
||||||
} $else {
|
} $else {
|
||||||
mut i := 0
|
mut i := 0
|
||||||
|
@ -56,6 +64,8 @@ pub fn string_from_wide(_wstr &u16) string {
|
||||||
// style string, encoded in UTF-16. It is more efficient, compared to
|
// style string, encoded in UTF-16. It is more efficient, compared to
|
||||||
// string_from_wide, but it requires you to know the input string length,
|
// string_from_wide, but it requires you to know the input string length,
|
||||||
// and to pass it as the second argument.
|
// and to pass it as the second argument.
|
||||||
|
// See also builtin.wchar.to_string2/2, for a version that eases working
|
||||||
|
// with the platform dependent &wchar_t L"" strings.
|
||||||
[manualfree; unsafe]
|
[manualfree; unsafe]
|
||||||
pub fn string_from_wide2(_wstr &u16, len int) string {
|
pub fn string_from_wide2(_wstr &u16, len int) string {
|
||||||
$if windows {
|
$if windows {
|
||||||
|
|
|
@ -72,6 +72,7 @@ fn test_string_from_wide2() {
|
||||||
|
|
||||||
fn test_reverse_cyrillic_with_string_from_wide() {
|
fn test_reverse_cyrillic_with_string_from_wide() {
|
||||||
s := 'Проба'
|
s := 'Проба'
|
||||||
z := unsafe { string_from_wide(s.to_wide()) }
|
ws := s.to_wide()
|
||||||
|
z := unsafe { string_from_wide(ws) }
|
||||||
assert z == s
|
assert z == s
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,20 @@ fn __memory_grow(size usize) usize
|
||||||
fn __memory_fill(dest &u8, value isize, size isize)
|
fn __memory_fill(dest &u8, value isize, size isize)
|
||||||
fn __memory_copy(dest &u8, src &u8, size isize)
|
fn __memory_copy(dest &u8, src &u8, size isize)
|
||||||
|
|
||||||
|
// add doc comments for the below functions
|
||||||
|
|
||||||
|
// __reinterpret_f32_u32 converts a `u32` to a `f32` without changing the bit pattern.
|
||||||
|
pub fn __reinterpret_f32_u32(v f32) u32
|
||||||
|
|
||||||
|
// __reinterpret_u32_f32 converts a `f32` to a `u32` without changing the bit pattern.
|
||||||
|
pub fn __reinterpret_u32_f32(v u32) f32
|
||||||
|
|
||||||
|
// __reinterpret_f64_u64 converts a `u64` to a `f64` without changing the bit pattern.
|
||||||
|
pub fn __reinterpret_f64_u64(v f64) u64
|
||||||
|
|
||||||
|
// __reinterpret_u64_f64 converts a `f64` to a `u64` without changing the bit pattern.
|
||||||
|
pub fn __reinterpret_u64_f64(v u64) f64
|
||||||
|
|
||||||
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
|
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
|
||||||
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
|
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
|
||||||
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
|
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub fn print(s string) {
|
||||||
len: usize(s.len)
|
len: usize(s.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
WASM.fd_write(1, &elm, 1, -1)
|
WASM.fd_write(1, &elm, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// println prints a message with a line end, to stdout.
|
// println prints a message with a line end, to stdout.
|
||||||
|
@ -20,7 +20,7 @@ pub fn println(s string) {
|
||||||
len: 1
|
len: 1
|
||||||
}]!
|
}]!
|
||||||
|
|
||||||
WASM.fd_write(1, &elm[0], 2, -1)
|
WASM.fd_write(1, &elm[0], 2, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eprint prints a message to stderr.
|
// eprint prints a message to stderr.
|
||||||
|
@ -30,7 +30,7 @@ pub fn eprint(s string) {
|
||||||
len: usize(s.len)
|
len: usize(s.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
WASM.fd_write(2, &elm, 1, -1)
|
WASM.fd_write(2, &elm, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eprintln prints a message with a line end, to stderr.
|
// eprintln prints a message with a line end, to stderr.
|
||||||
|
@ -43,7 +43,7 @@ pub fn eprintln(s string) {
|
||||||
len: 1
|
len: 1
|
||||||
}]!
|
}]!
|
||||||
|
|
||||||
WASM.fd_write(2, &elm[0], 2, -1)
|
WASM.fd_write(2, &elm[0], 2, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit terminates execution immediately and returns exit `code` to the shell.
|
// exit terminates execution immediately and returns exit `code` to the shell.
|
||||||
|
@ -57,6 +57,5 @@ pub fn exit(code int) {
|
||||||
pub fn panic(s string) {
|
pub fn panic(s string) {
|
||||||
eprint('V panic: ')
|
eprint('V panic: ')
|
||||||
eprintln(s)
|
eprintln(s)
|
||||||
_ := *&u8(-1)
|
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,21 +52,18 @@ fn (nn int) str_l(max int) string {
|
||||||
}
|
}
|
||||||
diff := max - index
|
diff := max - index
|
||||||
vmemmove(buf, voidptr(buf + index), diff + 1)
|
vmemmove(buf, voidptr(buf + index), diff + 1)
|
||||||
/*
|
|
||||||
// === manual memory move for bare metal ===
|
|
||||||
mut c:= 0
|
|
||||||
for c < diff {
|
|
||||||
buf[c] = buf[c+index]
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
buf[c] = 0
|
|
||||||
*/
|
|
||||||
return tos(buf, diff)
|
return tos(buf, diff)
|
||||||
|
|
||||||
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
|
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// str returns the value of the `u8` as a `string`.
|
||||||
|
// Example: assert u8(2).str() == '2'
|
||||||
|
pub fn (n u8) str() string {
|
||||||
|
return int(n).str_l(5)
|
||||||
|
}
|
||||||
|
|
||||||
// str returns the value of the `i8` as a `string`.
|
// str returns the value of the `i8` as a `string`.
|
||||||
// Example: assert i8(-2).str() == '-2'
|
// Example: assert i8(-2).str() == '-2'
|
||||||
pub fn (n i8) str() string {
|
pub fn (n i8) str() string {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
|
||||||
module builtin
|
module builtin
|
||||||
|
|
||||||
struct CIOVec {
|
struct CIOVec {
|
||||||
|
@ -9,6 +8,9 @@ struct CIOVec {
|
||||||
type Errno = u16
|
type Errno = u16
|
||||||
type FileDesc = int
|
type FileDesc = int
|
||||||
|
|
||||||
|
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
||||||
fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno
|
fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno
|
||||||
|
|
||||||
|
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
||||||
[noreturn]
|
[noreturn]
|
||||||
fn WASM.proc_exit(rval int)
|
fn WASM.proc_exit(rval int)
|
||||||
|
|
116
vlib/builtin/wchar/wchar.c.v
Normal file
116
vlib/builtin/wchar/wchar.c.v
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
module wchar
|
||||||
|
|
||||||
|
import strings
|
||||||
|
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
struct C.wchar_t {}
|
||||||
|
|
||||||
|
// Character is a type, that eases working with the platform dependent C.wchar_t type.
|
||||||
|
// Note: the size of C.wchar_t varies between platforms, it is 2 bytes on windows,
|
||||||
|
// and usually 4 bytes elsewhere.
|
||||||
|
pub type Character = C.wchar_t
|
||||||
|
|
||||||
|
// zero is a Character, that in C L"" strings represents the string end character (terminator).
|
||||||
|
pub const zero = from_rune(0)
|
||||||
|
|
||||||
|
// return a string representation of the given Character
|
||||||
|
pub fn (a Character) str() string {
|
||||||
|
return a.to_rune().str()
|
||||||
|
}
|
||||||
|
|
||||||
|
// == is an equality operator, to ease comparing Characters
|
||||||
|
// TODO: the default == operator, that V generates, does not work for C.wchar_t .
|
||||||
|
[inline]
|
||||||
|
pub fn (a Character) == (b Character) bool {
|
||||||
|
return u64(a) == u64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// to_rune creates a V rune, given a Character
|
||||||
|
[inline]
|
||||||
|
pub fn (c Character) to_rune() rune {
|
||||||
|
return unsafe { *(&rune(&c)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// from_rune creates a Character, given a V rune
|
||||||
|
[inline]
|
||||||
|
pub fn from_rune(r rune) Character {
|
||||||
|
return unsafe { *(&Character(&r)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// length_in_characters returns the length of the given wchar_t* wide C style L"" string.
|
||||||
|
// Example: assert unsafe { wchar.length_in_characters(wchar.from_string('abc')) } == 3
|
||||||
|
// See also `length_in_bytes` .
|
||||||
|
[unsafe]
|
||||||
|
pub fn length_in_characters(p voidptr) int {
|
||||||
|
mut len := 0
|
||||||
|
pc := &Character(p)
|
||||||
|
for unsafe { pc[len] != wchar.zero } {
|
||||||
|
len++
|
||||||
|
}
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
|
||||||
|
// length_in_bytes returns the length of the given wchar_t* wide C style L"" string in bytes.
|
||||||
|
// Note that the size of wchar_t is different on the different platforms, thus the length in
|
||||||
|
// bytes for the same data converted from UTF-8 to a &Character buffer, will be different as well.
|
||||||
|
// i.e. unsafe { wchar.length_in_bytes(wchar.from_string('abc')) } will be 12 on unix, but
|
||||||
|
// 6 on windows.
|
||||||
|
[unsafe]
|
||||||
|
pub fn length_in_bytes(p voidptr) int {
|
||||||
|
return unsafe { length_in_characters(p) } * int(sizeof(Character))
|
||||||
|
}
|
||||||
|
|
||||||
|
// to_string creates a V string, encoded in UTF-8, given a wchar_t*
|
||||||
|
// wide C style L"" string. It relies that the string has a 0 terminator at its end,
|
||||||
|
// to determine the string's length.
|
||||||
|
// Note, that the size of wchar_t is platform-dependent, and is *2 bytes* on windows,
|
||||||
|
// while it is *4 bytes* on most everything else.
|
||||||
|
// Unless you are interfacing with a C library, that does specifically use `wchar_t`,
|
||||||
|
// consider using `string_from_wide` instead, which will always assume that the input
|
||||||
|
// data is in an UTF-16 encoding, no matter what the platform is.
|
||||||
|
[unsafe]
|
||||||
|
pub fn to_string(p voidptr) string {
|
||||||
|
unsafe {
|
||||||
|
len := length_in_characters(p)
|
||||||
|
return to_string2(p, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to_string2 creates a V string, encoded in UTF-8, given a `C.wchar_t*`
|
||||||
|
// wide C style L"" string. Note, that the size of `C.wchar_t` is platform-dependent,
|
||||||
|
// and is *2 bytes* on windows, while *4* on most everything else.
|
||||||
|
// Unless you are interfacing with a C library, that does specifically use wchar_t,
|
||||||
|
// consider using string_from_wide2 instead, which will always assume that the input
|
||||||
|
// data is in an UTF-16 encoding, no matter what the platform is.
|
||||||
|
[manualfree; unsafe]
|
||||||
|
pub fn to_string2(p voidptr, len int) string {
|
||||||
|
pc := &Character(p)
|
||||||
|
mut sb := strings.new_builder(len)
|
||||||
|
defer {
|
||||||
|
unsafe { sb.free() }
|
||||||
|
}
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
u := unsafe { rune(pc[i]) }
|
||||||
|
sb.write_rune(u)
|
||||||
|
}
|
||||||
|
res := sb.str()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// from_string converts the V string (in UTF-8 encoding), into a newly allocated
|
||||||
|
// platform specific buffer of C.wchar_t .
|
||||||
|
// The conversion is done by processing each rune of the input string 1 by 1.
|
||||||
|
[manualfree]
|
||||||
|
pub fn from_string(s string) &Character {
|
||||||
|
srunes := s.runes()
|
||||||
|
unsafe {
|
||||||
|
mut result := &Character(vcalloc_noscan((srunes.len + 1) * int(sizeof(Character))))
|
||||||
|
for i, r in srunes {
|
||||||
|
result[i] = from_rune(r)
|
||||||
|
}
|
||||||
|
result[srunes.len] = wchar.zero
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
36
vlib/builtin/wchar/wchar_test.v
Normal file
36
vlib/builtin/wchar/wchar_test.v
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import builtin.wchar
|
||||||
|
|
||||||
|
const wide_serial_number_unix = [u16(67), 0, 76, 0, 52, 0, 54, 0, 73, 0, 49, 0, 65, 0, 48, 0, 48,
|
||||||
|
0, 54, 0, 52, 0, 57, 0, 0, 0, 0]
|
||||||
|
|
||||||
|
const wide_serial_number_windows = wide_serial_number_unix.map(u8(it))
|
||||||
|
|
||||||
|
const swide_serial_number = 'CL46I1A00649'
|
||||||
|
|
||||||
|
fn test_from_to_rune() {
|
||||||
|
for r in swide_serial_number.runes() {
|
||||||
|
c := wchar.from_rune(r)
|
||||||
|
assert c.to_rune() == r
|
||||||
|
}
|
||||||
|
assert wchar.from_rune(0).to_rune() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_to_string() {
|
||||||
|
mut p := voidptr(wide_serial_number_unix.data)
|
||||||
|
$if windows {
|
||||||
|
p = wide_serial_number_windows.data
|
||||||
|
}
|
||||||
|
assert unsafe { wchar.length_in_characters(p) } == swide_serial_number.len
|
||||||
|
s := unsafe { wchar.to_string(p) }
|
||||||
|
dump(s)
|
||||||
|
assert s == swide_serial_number
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_from_string() {
|
||||||
|
x := wchar.from_string(swide_serial_number)
|
||||||
|
assert unsafe { x[0] } == wchar.from_rune(`C`)
|
||||||
|
assert unsafe { x[1] } == wchar.from_rune(`L`)
|
||||||
|
assert unsafe { x[2] } == wchar.from_rune(`4`)
|
||||||
|
assert unsafe { x[11] } == wchar.from_rune(`9`)
|
||||||
|
assert unsafe { x[12] } == wchar.zero
|
||||||
|
}
|
|
@ -4,26 +4,27 @@ A module to provide eventing capabilities using pub/sub.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
1. `new()` - create a new `EventBus`
|
1. `new[T]()` - create a new `EventBus`
|
||||||
|
2. `EventBus.new[T]()` - create a new `EventBus`
|
||||||
|
|
||||||
### Structs:
|
### Structs:
|
||||||
|
|
||||||
**EventBus:**
|
**EventBus:**
|
||||||
|
|
||||||
1. `publish(name string, sender voidptr, args voidptr)` - publish an event with provided
|
1. `publish(name T, sender voidptr, args voidptr)` - publish an event with provided
|
||||||
Params & name
|
Params & name
|
||||||
2. `clear_all()` - clear all subscribers
|
2. `clear_all()` - clear all subscribers
|
||||||
3. `has_subscriber(name string)` - check if a subscriber to an event exists
|
3. `has_subscriber(name T)` - check if a subscriber to an event exists
|
||||||
|
|
||||||
**Subscriber:**
|
**Subscriber:**
|
||||||
|
|
||||||
1. `subscribe(name string, handler EventHandlerFn)` - subscribe to an event
|
1. `subscribe(name T, handler EventHandlerFn)` - subscribe to an event
|
||||||
2. `subscribe_once(name string, handler EventHandlerFn)` - subscribe only once to an event
|
2. `subscribe_once(name T, handler EventHandlerFn)` - subscribe only once to an event
|
||||||
3. `subscribe_method(name string, handler EventHandlerFn, receiver voidptr)` - subscribe to
|
3. `subscribe_method(name T, handler EventHandlerFn, receiver voidptr)` - subscribe to
|
||||||
an event and also set the `receiver` as a parameter.
|
an event and also set the `receiver` as a parameter.
|
||||||
Since it's not yet possible to send methods as parameters, this is a workaround.
|
Since it's not yet possible to send methods as parameters, this is a workaround.
|
||||||
4. `is_subscribed(name string)` - check if we are subscribed to an event
|
4. `is_subscribed(name T)` - check if we are subscribed to an event
|
||||||
5. `unsubscribe(name string)` - unsubscribe from an event
|
5. `unsubscribe(name T)` - unsubscribe from an event
|
||||||
|
|
||||||
**Event Handler Signature:**
|
**Event Handler Signature:**
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ import eventbus
|
||||||
|
|
||||||
// initialize it globally
|
// initialize it globally
|
||||||
const (
|
const (
|
||||||
eb = eventbus.new()
|
eb = eventbus.new[string]()
|
||||||
)
|
)
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -88,7 +89,7 @@ module main
|
||||||
|
|
||||||
import eventbus
|
import eventbus
|
||||||
|
|
||||||
const eb = eventbus.new()
|
const eb = eventbus.new[string]()
|
||||||
|
|
||||||
struct Work {
|
struct Work {
|
||||||
hours int
|
hours int
|
||||||
|
|
|
@ -2,59 +2,70 @@ module eventbus
|
||||||
|
|
||||||
pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr)
|
pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr)
|
||||||
|
|
||||||
pub struct Publisher {
|
pub struct Publisher[T] {
|
||||||
mut:
|
mut:
|
||||||
registry &Registry = unsafe { nil }
|
registry &Registry[T] = unsafe { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Subscriber {
|
pub struct Subscriber[T] {
|
||||||
mut:
|
mut:
|
||||||
registry &Registry = unsafe { nil }
|
registry &Registry[T] = unsafe { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Registry {
|
struct Registry[T] {
|
||||||
mut:
|
mut:
|
||||||
events []EventHandler
|
events []EventHandler[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventHandler {
|
struct EventHandler[T] {
|
||||||
name string
|
name T
|
||||||
handler EventHandlerFn
|
handler EventHandlerFn
|
||||||
receiver voidptr = unsafe { nil }
|
receiver voidptr = unsafe { nil }
|
||||||
once bool
|
once bool
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventBus {
|
pub struct EventBus[T] {
|
||||||
pub mut:
|
pub mut:
|
||||||
registry &Registry = unsafe { nil }
|
registry &Registry[T] = unsafe { nil }
|
||||||
publisher &Publisher = unsafe { nil }
|
publisher &Publisher[T] = unsafe { nil }
|
||||||
subscriber &Subscriber = unsafe { nil }
|
subscriber &Subscriber[T] = unsafe { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() &EventBus {
|
// EventBus.new[T] create a new eventbus with event type T.
|
||||||
registry := &Registry{
|
pub fn EventBus.new[T]() &EventBus[T] {
|
||||||
|
registry := &Registry[T]{
|
||||||
events: []
|
events: []
|
||||||
}
|
}
|
||||||
return &EventBus{registry, &Publisher{registry}, &Subscriber{registry}}
|
return &EventBus[T]{registry, &Publisher[T]{registry}, &Subscriber[T]{registry}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventBus Methods
|
// new[T] create a new eventbus with event type T.
|
||||||
pub fn (eb &EventBus) publish(name string, sender voidptr, args voidptr) {
|
pub fn new[T]() &EventBus[T] {
|
||||||
|
registry := &Registry[T]{
|
||||||
|
events: []
|
||||||
|
}
|
||||||
|
return &EventBus[T]{registry, &Publisher[T]{registry}, &Subscriber[T]{registry}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish publish an event with provided Params & name.
|
||||||
|
pub fn (eb &EventBus[T]) publish(name T, sender voidptr, args voidptr) {
|
||||||
mut publisher := eb.publisher
|
mut publisher := eb.publisher
|
||||||
publisher.publish(name, sender, args)
|
publisher.publish(name, sender, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (eb &EventBus) clear_all() {
|
// clear_all clear all subscribers.
|
||||||
|
pub fn (eb &EventBus[T]) clear_all() {
|
||||||
mut publisher := eb.publisher
|
mut publisher := eb.publisher
|
||||||
publisher.clear_all()
|
publisher.clear_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (eb &EventBus) has_subscriber(name string) bool {
|
// has_subscriber check if a subscriber to an event exists.
|
||||||
|
pub fn (eb &EventBus[T]) has_subscriber(name T) bool {
|
||||||
return eb.registry.check_subscriber(name)
|
return eb.registry.check_subscriber(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publisher Methods
|
// publish publish an event with provided Params & name.
|
||||||
fn (mut pb Publisher) publish(name string, sender voidptr, args voidptr) {
|
fn (mut pb Publisher[T]) publish(name T, sender voidptr, args voidptr) {
|
||||||
for event in pb.registry.events {
|
for event in pb.registry.events {
|
||||||
if event.name == name {
|
if event.name == name {
|
||||||
event.handler(event.receiver, args, sender)
|
event.handler(event.receiver, args, sender)
|
||||||
|
@ -63,59 +74,64 @@ fn (mut pb Publisher) publish(name string, sender voidptr, args voidptr) {
|
||||||
pb.registry.events = pb.registry.events.filter(!(it.name == name && it.once))
|
pb.registry.events = pb.registry.events.filter(!(it.name == name && it.once))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut p Publisher) clear_all() {
|
// clear_all clear all subscribers.
|
||||||
|
fn (mut p Publisher[T]) clear_all() {
|
||||||
p.registry.events.clear()
|
p.registry.events.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscriber Methods
|
// subscribe subscribe to an event `name`.
|
||||||
pub fn (mut s Subscriber) subscribe(name string, handler EventHandlerFn) {
|
pub fn (mut s Subscriber[T]) subscribe(name T, handler EventHandlerFn) {
|
||||||
s.registry.events << EventHandler{
|
s.registry.events << EventHandler[T]{
|
||||||
name: name
|
name: name
|
||||||
handler: handler
|
handler: handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut s Subscriber) subscribe_method(name string, handler EventHandlerFn, receiver voidptr) {
|
// subscribe_method subscribe to an event `name` and also set the `receiver` as a parameter.
|
||||||
s.registry.events << EventHandler{
|
pub fn (mut s Subscriber[T]) subscribe_method(name T, handler EventHandlerFn, receiver voidptr) {
|
||||||
|
s.registry.events << EventHandler[T]{
|
||||||
name: name
|
name: name
|
||||||
handler: handler
|
handler: handler
|
||||||
receiver: receiver
|
receiver: receiver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsubscribe_method unsubscribe a receiver for only one method
|
// unsubscribe_method unsubscribe a receiver for only one method.
|
||||||
pub fn (mut s Subscriber) unsubscribe_method(name string, receiver voidptr) {
|
pub fn (mut s Subscriber[T]) unsubscribe_method(name T, receiver voidptr) {
|
||||||
s.registry.events = s.registry.events.filter(!(it.name == name && it.receiver == receiver))
|
s.registry.events = s.registry.events.filter(!(it.name == name && it.receiver == receiver))
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsubscribe_receiver unsubscribes a receiver from all events
|
// unsubscribe_receiver unsubscribes a receiver from all events.
|
||||||
pub fn (mut s Subscriber) unsubscribe_receiver(receiver voidptr) {
|
pub fn (mut s Subscriber[T]) unsubscribe_receiver(receiver voidptr) {
|
||||||
s.registry.events = s.registry.events.filter(it.receiver != receiver)
|
s.registry.events = s.registry.events.filter(it.receiver != receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut s Subscriber) subscribe_once(name string, handler EventHandlerFn) {
|
// subscribe_once subscribe only once to an event `name`.
|
||||||
s.registry.events << EventHandler{
|
pub fn (mut s Subscriber[T]) subscribe_once(name T, handler EventHandlerFn) {
|
||||||
|
s.registry.events << EventHandler[T]{
|
||||||
name: name
|
name: name
|
||||||
handler: handler
|
handler: handler
|
||||||
once: true
|
once: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (s &Subscriber) is_subscribed(name string) bool {
|
// is_subscribed check if we are subscribed to an event `name`.
|
||||||
|
pub fn (s &Subscriber[T]) is_subscribed(name T) bool {
|
||||||
return s.registry.check_subscriber(name)
|
return s.registry.check_subscriber(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// is_subscribed_method checks whether a receiver was already subscribed for any events
|
// is_subscribed_method checks whether a receiver was already subscribed for any events.
|
||||||
pub fn (s &Subscriber) is_subscribed_method(name string, receiver voidptr) bool {
|
pub fn (s &Subscriber[T]) is_subscribed_method(name T, receiver voidptr) bool {
|
||||||
return s.registry.events.any(it.name == name && it.receiver == receiver)
|
return s.registry.events.any(it.name == name && it.receiver == receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut s Subscriber) unsubscribe(name string, handler EventHandlerFn) {
|
// unsubscribe unsubscribe from an event `name`.
|
||||||
|
pub fn (mut s Subscriber[T]) unsubscribe(name T, handler EventHandlerFn) {
|
||||||
// v := voidptr(handler)
|
// v := voidptr(handler)
|
||||||
s.registry.events = s.registry.events.filter(!(it.name == name && it.handler == handler))
|
s.registry.events = s.registry.events.filter(!(it.name == name && it.handler == handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registry Methods
|
// Registry Methods
|
||||||
fn (r &Registry) check_subscriber(name string) bool {
|
fn (r &Registry[T]) check_subscriber(name T) bool {
|
||||||
return r.events.any(it.name == name)
|
return r.events.any(it.name == name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,12 @@ struct FakeReceiver {
|
||||||
ok bool
|
ok bool
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_eventbus() {
|
fn test_eventbus_string() {
|
||||||
ev_data := &EventData{'hello'}
|
ev_data := &EventData{'hello'}
|
||||||
mut eb := eventbus.new()
|
mut eb := eventbus.new[string]()
|
||||||
eb.subscriber.subscribe_once('on_test', on_test)
|
eb.subscriber.subscribe_once('on_test', on_test)
|
||||||
assert eb.has_subscriber('on_test')
|
assert eb.has_subscriber('on_test')
|
||||||
|
assert !eb.has_subscriber('not_exist')
|
||||||
assert eb.subscriber.is_subscribed('on_test')
|
assert eb.subscriber.is_subscribed('on_test')
|
||||||
eb.publish('on_test', eb, ev_data)
|
eb.publish('on_test', eb, ev_data)
|
||||||
assert !eb.has_subscriber('on_test')
|
assert !eb.has_subscriber('on_test')
|
||||||
|
@ -25,10 +26,52 @@ fn test_eventbus() {
|
||||||
assert !eb.subscriber.is_subscribed('on_test')
|
assert !eb.subscriber.is_subscribed('on_test')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Events {
|
||||||
|
event_1
|
||||||
|
event_2
|
||||||
|
event_3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_eventbus_enum() {
|
||||||
|
ev_data := &EventData{'hello'}
|
||||||
|
mut eb := eventbus.EventBus.new[Events]()
|
||||||
|
eb.subscriber.subscribe_once(Events.event_1, on_test)
|
||||||
|
assert eb.has_subscriber(Events.event_1)
|
||||||
|
assert !eb.has_subscriber(Events.event_2)
|
||||||
|
assert eb.subscriber.is_subscribed(Events.event_1)
|
||||||
|
eb.publish(Events.event_1, eb, ev_data)
|
||||||
|
assert !eb.has_subscriber(Events.event_1)
|
||||||
|
assert !eb.subscriber.is_subscribed(Events.event_1)
|
||||||
|
eb.subscriber.subscribe(Events.event_1, on_test)
|
||||||
|
assert eb.has_subscriber(Events.event_1)
|
||||||
|
assert eb.subscriber.is_subscribed(Events.event_1)
|
||||||
|
eb.clear_all()
|
||||||
|
assert !eb.has_subscriber(Events.event_1)
|
||||||
|
assert !eb.subscriber.is_subscribed(Events.event_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_eventbus_int() {
|
||||||
|
ev_data := &EventData{'hello'}
|
||||||
|
mut eb := eventbus.EventBus.new[int]()
|
||||||
|
eb.subscriber.subscribe_once(9999, on_test)
|
||||||
|
assert eb.has_subscriber(9999)
|
||||||
|
assert !eb.has_subscriber(1111)
|
||||||
|
assert eb.subscriber.is_subscribed(9999)
|
||||||
|
eb.publish(9999, eb, ev_data)
|
||||||
|
assert !eb.has_subscriber(9999)
|
||||||
|
assert !eb.subscriber.is_subscribed(9999)
|
||||||
|
eb.subscriber.subscribe(9999, on_test)
|
||||||
|
assert eb.has_subscriber(9999)
|
||||||
|
assert eb.subscriber.is_subscribed(9999)
|
||||||
|
eb.clear_all()
|
||||||
|
assert !eb.has_subscriber(9999)
|
||||||
|
assert !eb.subscriber.is_subscribed(9999)
|
||||||
|
}
|
||||||
|
|
||||||
fn test_subscribe_method() {
|
fn test_subscribe_method() {
|
||||||
// Does not really test subscribe_method idinvidually though
|
// Does not really test subscribe_method idinvidually though
|
||||||
// given
|
// given
|
||||||
mut eb := eventbus.new()
|
mut eb := eventbus.new[string]()
|
||||||
r := FakeReceiver{}
|
r := FakeReceiver{}
|
||||||
|
|
||||||
assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
||||||
|
@ -41,7 +84,7 @@ fn test_subscribe_method() {
|
||||||
|
|
||||||
fn test_unsubscribe_method() {
|
fn test_unsubscribe_method() {
|
||||||
// given
|
// given
|
||||||
mut eb := eventbus.new()
|
mut eb := eventbus.new[string]()
|
||||||
r := FakeReceiver{}
|
r := FakeReceiver{}
|
||||||
r2 := FakeReceiver{}
|
r2 := FakeReceiver{}
|
||||||
|
|
||||||
|
@ -58,7 +101,7 @@ fn test_unsubscribe_method() {
|
||||||
fn test_publish() {
|
fn test_publish() {
|
||||||
// given
|
// given
|
||||||
ev_data := &EventData{'hello'}
|
ev_data := &EventData{'hello'}
|
||||||
mut eb := eventbus.new()
|
mut eb := eventbus.new[string]()
|
||||||
|
|
||||||
// when
|
// when
|
||||||
eb.subscriber.subscribe_once('on_test', on_test)
|
eb.subscriber.subscribe_once('on_test', on_test)
|
||||||
|
@ -71,7 +114,7 @@ fn test_publish() {
|
||||||
|
|
||||||
fn test_publish_with_receiver() {
|
fn test_publish_with_receiver() {
|
||||||
// given
|
// given
|
||||||
mut eb := eventbus.new()
|
mut eb := eventbus.new[string]()
|
||||||
ev_data := &EventData{'hello'}
|
ev_data := &EventData{'hello'}
|
||||||
r := FakeReceiver{}
|
r := FakeReceiver{}
|
||||||
|
|
||||||
|
@ -85,7 +128,7 @@ fn test_publish_with_receiver() {
|
||||||
|
|
||||||
fn test_unsubscribe_reveiver() {
|
fn test_unsubscribe_reveiver() {
|
||||||
// given
|
// given
|
||||||
mut eb := eventbus.new()
|
mut eb := eventbus.new[string]()
|
||||||
r := &FakeReceiver{}
|
r := &FakeReceiver{}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
|
|
@ -530,8 +530,56 @@ pub fn (ctx &Context) begin() {
|
||||||
sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0)
|
sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// end finishes drawing for the context.
|
pub enum EndEnum {
|
||||||
pub fn (ctx &Context) end() {
|
clear
|
||||||
|
passthru
|
||||||
|
}
|
||||||
|
|
||||||
|
[params]
|
||||||
|
pub struct EndOptions {
|
||||||
|
how EndEnum
|
||||||
|
}
|
||||||
|
|
||||||
|
const dontcare_pass = gfx.PassAction{
|
||||||
|
colors: [
|
||||||
|
gfx.ColorAttachmentAction{
|
||||||
|
action: .dontcare
|
||||||
|
value: gfx.Color{1.0, 1.0, 1.0, 1.0}
|
||||||
|
},
|
||||||
|
gfx.ColorAttachmentAction{
|
||||||
|
action: .dontcare
|
||||||
|
value: gfx.Color{1.0, 1.0, 1.0, 1.0}
|
||||||
|
},
|
||||||
|
gfx.ColorAttachmentAction{
|
||||||
|
action: .dontcare
|
||||||
|
value: gfx.Color{1.0, 1.0, 1.0, 1.0}
|
||||||
|
},
|
||||||
|
gfx.ColorAttachmentAction{
|
||||||
|
action: .dontcare
|
||||||
|
value: gfx.Color{1.0, 1.0, 1.0, 1.0}
|
||||||
|
},
|
||||||
|
]!
|
||||||
|
}
|
||||||
|
|
||||||
|
// end finishes all the drawing for the context ctx.
|
||||||
|
// All accumulated draw calls before ctx.end(), will be done in a separate Sokol pass.
|
||||||
|
//
|
||||||
|
// Note: each Sokol pass, has a limit on the number of draw calls, that can be done in it.
|
||||||
|
// Once that limit is reached, the whole pass will not draw anything, which can be frustrating.
|
||||||
|
//
|
||||||
|
// To overcome this limitation, you may use *several passes*, when you want to make thousands
|
||||||
|
// of draw calls (for example, if you need to draw thousands of circles/rectangles/sprites etc),
|
||||||
|
// where each pass will render just a limited amount of primitives.
|
||||||
|
//
|
||||||
|
// In the context of the gg module (without dropping to using sgl and gfx directly), it means,
|
||||||
|
// that you will need a new pair of ctx.begin() and ctx.end() calls, surrounding all the draw
|
||||||
|
// calls, that should be done in each pass.
|
||||||
|
//
|
||||||
|
// The default ctx.end() is equivalent to ctx.end(how:.clear). It will erase the existing
|
||||||
|
// rendered content with the background color, before drawing anything else.
|
||||||
|
// You can call ctx.end(how:.passthru) for a pass, that *will not* erase the previously
|
||||||
|
// rendered content in the context.
|
||||||
|
pub fn (ctx &Context) end(options EndOptions) {
|
||||||
$if show_fps ? {
|
$if show_fps ? {
|
||||||
ctx.show_fps()
|
ctx.show_fps()
|
||||||
} $else {
|
} $else {
|
||||||
|
@ -539,7 +587,14 @@ pub fn (ctx &Context) end() {
|
||||||
ctx.show_fps()
|
ctx.show_fps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gfx.begin_default_pass(ctx.clear_pass, sapp.width(), sapp.height())
|
match options.how {
|
||||||
|
.clear {
|
||||||
|
gfx.begin_default_pass(ctx.clear_pass, sapp.width(), sapp.height())
|
||||||
|
}
|
||||||
|
.passthru {
|
||||||
|
gfx.begin_default_pass(gg.dontcare_pass, sapp.width(), sapp.height())
|
||||||
|
}
|
||||||
|
}
|
||||||
sgl.draw()
|
sgl.draw()
|
||||||
gfx.end_pass()
|
gfx.end_pass()
|
||||||
gfx.commit()
|
gfx.commit()
|
||||||
|
|
41
vlib/json/json_option_alias_test.v
Normal file
41
vlib/json/json_option_alias_test.v
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
optional_alias ?MyAlias // primitive
|
||||||
|
optional_struct ?MyAlias2 // complex
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Complex {
|
||||||
|
a int = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyAlias = int
|
||||||
|
type MyAlias2 = Complex
|
||||||
|
|
||||||
|
fn test_empty() {
|
||||||
|
test := Test{}
|
||||||
|
encoded := json.encode(test)
|
||||||
|
assert dump(encoded) == '{}'
|
||||||
|
assert json.decode(Test, '{}')! == test
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_value() {
|
||||||
|
test := Test{
|
||||||
|
optional_alias: 1
|
||||||
|
}
|
||||||
|
encoded := json.encode(test)
|
||||||
|
assert dump(encoded) == '{"optional_alias":1}'
|
||||||
|
assert json.decode(Test, '{"optional_alias":1}')! == test
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_value_2() {
|
||||||
|
test := Test{
|
||||||
|
optional_alias: 1
|
||||||
|
optional_struct: Complex{
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoded := json.encode(test)
|
||||||
|
assert dump(encoded) == '{"optional_alias":1,"optional_struct":{"a":1}}'
|
||||||
|
assert json.decode(Test, '{"optional_alias":1,"optional_struct":{"a":1}}')! == test
|
||||||
|
}
|
46
vlib/json/json_option_none_test.v
Normal file
46
vlib/json/json_option_none_test.v
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
struct Struct {
|
||||||
|
a int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
a ?int
|
||||||
|
b ?string
|
||||||
|
c ?Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_main() {
|
||||||
|
a := json.decode(Test, '{"a": 1, "b": "foo"}')!
|
||||||
|
dump(a)
|
||||||
|
|
||||||
|
assert a.a != none
|
||||||
|
assert a.b != none
|
||||||
|
|
||||||
|
b := json.decode(Test, '{"a": 1}')!
|
||||||
|
dump(b)
|
||||||
|
assert b.a != none
|
||||||
|
assert b.b == none
|
||||||
|
|
||||||
|
c := json.decode(Test, '{"a": 1, "b": null}')!
|
||||||
|
dump(b)
|
||||||
|
assert c.a != none
|
||||||
|
assert c.b == none
|
||||||
|
|
||||||
|
d := json.decode(Test, '{"a": null, "b": null}')!
|
||||||
|
dump(d)
|
||||||
|
assert d.a == none
|
||||||
|
assert d.b == none
|
||||||
|
|
||||||
|
e := json.decode(Test, '{"a": null, "b": null, "c": null}')!
|
||||||
|
dump(e)
|
||||||
|
assert e.a == none
|
||||||
|
assert e.b == none
|
||||||
|
assert e.c == none
|
||||||
|
|
||||||
|
f := json.decode(Test, '{"a": null, "b": null, "c": {"a":1}}')!
|
||||||
|
dump(f)
|
||||||
|
assert f.a == none
|
||||||
|
assert f.b == none
|
||||||
|
assert f.c != none
|
||||||
|
}
|
|
@ -227,8 +227,14 @@ pub fn (mut zftp FTP) dir() ![]string {
|
||||||
mut dir := []string{}
|
mut dir := []string{}
|
||||||
sdir := list_dir.bytestr()
|
sdir := list_dir.bytestr()
|
||||||
for lfile in sdir.split('\n') {
|
for lfile in sdir.split('\n') {
|
||||||
|
if lfile.len > 56 {
|
||||||
|
dir << lfile#[56..lfile.len - 1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
if lfile.len > 1 {
|
if lfile.len > 1 {
|
||||||
dir << lfile.after(' ').trim_space()
|
trimmed := lfile.after(':')
|
||||||
|
dir << trimmed#[3..trimmed.len - 1]
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dir
|
return dir
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
## Description:
|
## Description:
|
||||||
|
|
||||||
`picoev` is a thin wrapper over [picoev](https://github.com/kazuho/picoev),
|
`picoev` is a V implementation of [picoev](https://github.com/kazuho/picoev),
|
||||||
which in turn is "A tiny, lightning fast event loop for network applications".
|
which in turn is "A tiny, lightning fast event loop for network applications".
|
||||||
|
|
95
vlib/picoev/loop_default.c.v
Normal file
95
vlib/picoev/loop_default.c.v
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
module picoev
|
||||||
|
|
||||||
|
$if windows {
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
} $else {
|
||||||
|
#include <sys/select.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelectLoop {
|
||||||
|
mut:
|
||||||
|
id int
|
||||||
|
now i64
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoopType = SelectLoop
|
||||||
|
|
||||||
|
// create_select_loop creates a `SelectLoop` struct with `id`
|
||||||
|
pub fn create_select_loop(id int) !&SelectLoop {
|
||||||
|
return &SelectLoop{
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) update_events(fd int, events int) int {
|
||||||
|
// check if fd is in range
|
||||||
|
assert fd < max_fds
|
||||||
|
|
||||||
|
pv.file_descriptors[fd].events = u32(events & picoev_readwrite)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) poll_once(max_wait int) int {
|
||||||
|
readfds, writefds, errorfds := C.fd_set{}, C.fd_set{}, C.fd_set{}
|
||||||
|
|
||||||
|
// setup
|
||||||
|
C.FD_ZERO(&readfds)
|
||||||
|
C.FD_ZERO(&writefds)
|
||||||
|
C.FD_ZERO(&errorfds)
|
||||||
|
|
||||||
|
mut maxfd := 0
|
||||||
|
|
||||||
|
// find the maximum socket for `select` and add sockets to the fd_sets
|
||||||
|
for target in pv.file_descriptors {
|
||||||
|
if target.loop_id == pv.loop.id {
|
||||||
|
if target.events & picoev_read != 0 {
|
||||||
|
C.FD_SET(target.fd, &readfds)
|
||||||
|
if maxfd < target.fd {
|
||||||
|
maxfd = target.fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target.events & picoev_write != 0 {
|
||||||
|
C.FD_SET(target.fd, &writefds)
|
||||||
|
if maxfd < target.fd {
|
||||||
|
maxfd = target.fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select and handle sockets if any
|
||||||
|
tv := C.timeval{
|
||||||
|
tv_sec: u64(max_wait)
|
||||||
|
tv_usec: 0
|
||||||
|
}
|
||||||
|
r := C.@select(maxfd + 1, &readfds, &writefds, &errorfds, &tv)
|
||||||
|
if r == -1 {
|
||||||
|
// timeout
|
||||||
|
return -1
|
||||||
|
} else if r > 0 {
|
||||||
|
for target in pv.file_descriptors {
|
||||||
|
if target.loop_id == pv.loop.id {
|
||||||
|
// vfmt off
|
||||||
|
read_events := (
|
||||||
|
(if C.FD_ISSET(target.fd, &readfds) { picoev_read } else { 0 })
|
||||||
|
|
|
||||||
|
(if C.FD_ISSET(target.fd, &writefds) { picoev_write } else { 0 })
|
||||||
|
)
|
||||||
|
// vfmt on
|
||||||
|
if read_events != 0 {
|
||||||
|
$if trace_fd ? {
|
||||||
|
eprintln('do callback ${target.fd}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// do callback!
|
||||||
|
unsafe { target.cb(target.fd, read_events, &pv) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
203
vlib/picoev/loop_freebsd.c.v
Normal file
203
vlib/picoev/loop_freebsd.c.v
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
module picoev
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/event.h>
|
||||||
|
|
||||||
|
fn C.kevent(int, changelist voidptr, nchanges int, eventlist voidptr, nevents int, timout &C.timespec) int
|
||||||
|
fn C.kqueue() int
|
||||||
|
fn C.EV_SET(kev voidptr, ident int, filter i16, flags u16, fflags u32, data voidptr, udata voidptr)
|
||||||
|
|
||||||
|
pub struct C.kevent {
|
||||||
|
pub mut:
|
||||||
|
ident int
|
||||||
|
// uintptr_t
|
||||||
|
filter i16
|
||||||
|
flags u16
|
||||||
|
fflags u32
|
||||||
|
data voidptr
|
||||||
|
// intptr_t
|
||||||
|
udata voidptr
|
||||||
|
}
|
||||||
|
|
||||||
|
[heap]
|
||||||
|
pub struct KqueueLoop {
|
||||||
|
mut:
|
||||||
|
id int
|
||||||
|
now i64
|
||||||
|
kq_id int
|
||||||
|
// -1 if not changed
|
||||||
|
changed_fds int
|
||||||
|
events [1024]C.kevent
|
||||||
|
changelist [256]C.kevent
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoopType = KqueueLoop
|
||||||
|
|
||||||
|
// create_kqueue_loop creates a new kernel event queue with loop_id=`id`
|
||||||
|
pub fn create_kqueue_loop(id int) !&KqueueLoop {
|
||||||
|
mut loop := &KqueueLoop{
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.kq_id = C.kqueue()
|
||||||
|
if loop.kq_id == -1 {
|
||||||
|
return error('could not create kqueue loop!')
|
||||||
|
}
|
||||||
|
loop.changed_fds = -1
|
||||||
|
return loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// ev_set sets a new `kevent` with file descriptor `index`
|
||||||
|
[inline]
|
||||||
|
pub fn (mut pv Picoev) ev_set(index int, operation int, events int) {
|
||||||
|
// vfmt off
|
||||||
|
filter := i16(
|
||||||
|
(if events & picoev_read != 0 { C.EVFILT_READ } else { 0 })
|
||||||
|
|
|
||||||
|
(if events & picoev_write != 0 { C.EVFILT_WRITE } else { 0 })
|
||||||
|
)
|
||||||
|
// vfmt on
|
||||||
|
C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0,
|
||||||
|
0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// backend_build uses the lower 8 bits to store the old events and the higher 8
|
||||||
|
// bits to store the next file descriptor in `Target.backend`
|
||||||
|
[inline]
|
||||||
|
fn backend_build(next_fd int, events u32) int {
|
||||||
|
return int((u32(next_fd) << 8) | (events & 0xff))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the lower 8 bits
|
||||||
|
[inline]
|
||||||
|
fn backend_get_old_events(backend int) int {
|
||||||
|
return backend & 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the higher 8 bits
|
||||||
|
[inline]
|
||||||
|
fn backend_get_next_fd(backend int) int {
|
||||||
|
return backend >> 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply pending processes all changes for the file descriptors and updates `loop.changelist`
|
||||||
|
// if `aplly_all` is `true` the changes are immediately applied
|
||||||
|
fn (mut pv Picoev) apply_pending_changes(apply_all bool) int {
|
||||||
|
mut total, mut nevents := 0, 0
|
||||||
|
|
||||||
|
for pv.loop.changed_fds != -1 {
|
||||||
|
mut target := pv.file_descriptors[pv.loop.changed_fds]
|
||||||
|
old_events := backend_get_old_events(target.backend)
|
||||||
|
if target.events != old_events {
|
||||||
|
// events have been changed
|
||||||
|
if old_events != 0 {
|
||||||
|
pv.ev_set(total, C.EV_DISABLE, old_events)
|
||||||
|
total++
|
||||||
|
}
|
||||||
|
if target.events != 0 {
|
||||||
|
pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events))
|
||||||
|
total++
|
||||||
|
}
|
||||||
|
// Apply the changes if the total changes exceed the changelist size
|
||||||
|
if total + 1 >= pv.loop.changelist.len {
|
||||||
|
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL,
|
||||||
|
0, C.NULL)
|
||||||
|
assert nevents == 0
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pv.loop.changed_fds = backend_get_next_fd(target.backend)
|
||||||
|
target.backend = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if apply_all && total != 0 {
|
||||||
|
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL)
|
||||||
|
assert nevents == 0
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) update_events(fd int, events int) int {
|
||||||
|
// check if fd is in range
|
||||||
|
assert fd < max_fds
|
||||||
|
|
||||||
|
mut target := pv.file_descriptors[fd]
|
||||||
|
|
||||||
|
// initialize if adding the fd
|
||||||
|
if events & picoev_add != 0 {
|
||||||
|
target.backend = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// return if nothing to do
|
||||||
|
if (events == picoev_del && target.backend == -1)
|
||||||
|
|| (events != picoev_del && events & picoev_readwrite == target.events) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to changed list if not yet being done
|
||||||
|
if target.backend == -1 {
|
||||||
|
target.backend = backend_build(pv.loop.changed_fds, target.events)
|
||||||
|
pv.loop.changed_fds = fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// update events
|
||||||
|
target.events = u32(events & picoev_readwrite)
|
||||||
|
// apply immediately if is a DELETE
|
||||||
|
if events & picoev_del != 0 {
|
||||||
|
pv.apply_pending_changes(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) poll_once(max_wait int) int {
|
||||||
|
ts := C.timespec{
|
||||||
|
tv_sec: max_wait
|
||||||
|
tv_nsec: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mut total, mut nevents := 0, 0
|
||||||
|
// apply changes later when the callback is called.
|
||||||
|
total = pv.apply_pending_changes(false)
|
||||||
|
|
||||||
|
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, pv.loop.events.len,
|
||||||
|
&ts)
|
||||||
|
if nevents == -1 {
|
||||||
|
// the errors we can only rescue
|
||||||
|
assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < nevents; i++ {
|
||||||
|
event := pv.loop.events[i]
|
||||||
|
target := pv.file_descriptors[event.ident]
|
||||||
|
|
||||||
|
// changelist errors are fatal
|
||||||
|
assert event.flags & C.EV_ERROR == 0
|
||||||
|
|
||||||
|
if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 {
|
||||||
|
read_events := match int(event.filter) {
|
||||||
|
C.EVFILT_READ {
|
||||||
|
picoev_read
|
||||||
|
}
|
||||||
|
C.EVFILT_WRITE {
|
||||||
|
picoev_write
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do callback!
|
||||||
|
unsafe { target.cb(target.fd, read_events, &pv) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
139
vlib/picoev/loop_linux.c.v
Normal file
139
vlib/picoev/loop_linux.c.v
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
module picoev
|
||||||
|
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
fn C.epoll_create(int) int
|
||||||
|
fn C.epoll_wait(int, voidptr, int, int) int
|
||||||
|
fn C.epoll_ctl(int, int, int, voidptr) int
|
||||||
|
|
||||||
|
[typedef]
|
||||||
|
pub union C.epoll_data_t {
|
||||||
|
mut:
|
||||||
|
ptr voidptr
|
||||||
|
fd int
|
||||||
|
u32 u32
|
||||||
|
u64 u64
|
||||||
|
}
|
||||||
|
|
||||||
|
[packed]
|
||||||
|
pub struct C.epoll_event {
|
||||||
|
mut:
|
||||||
|
events u32
|
||||||
|
data C.epoll_data_t
|
||||||
|
}
|
||||||
|
|
||||||
|
[heap]
|
||||||
|
pub struct EpollLoop {
|
||||||
|
mut:
|
||||||
|
id int
|
||||||
|
epoll_fd int
|
||||||
|
events [1024]C.epoll_event
|
||||||
|
now i64
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoopType = EpollLoop
|
||||||
|
|
||||||
|
// create_epoll_loop creates a new epoll instance for and returns an
|
||||||
|
// `EpollLoop` struct with `id`
|
||||||
|
pub fn create_epoll_loop(id int) !&EpollLoop {
|
||||||
|
mut loop := &EpollLoop{
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.epoll_fd = C.epoll_create(max_fds)
|
||||||
|
if loop.epoll_fd == -1 {
|
||||||
|
return error('could not create epoll loop!')
|
||||||
|
}
|
||||||
|
|
||||||
|
return loop
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) update_events(fd int, events int) int {
|
||||||
|
// check if fd is in range
|
||||||
|
assert fd < max_fds
|
||||||
|
|
||||||
|
mut target := pv.file_descriptors[fd]
|
||||||
|
mut ev := C.epoll_event{}
|
||||||
|
|
||||||
|
// fd belongs to loop
|
||||||
|
if events & picoev_del != target.events && target.loop_id != pv.loop.id {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if events & picoev_readwrite == target.events {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// vfmt off
|
||||||
|
ev.events = u32(
|
||||||
|
(if events & picoev_read != 0 { C.EPOLLIN } else { 0 })
|
||||||
|
|
|
||||||
|
(if events & picoev_write != 0 { C.EPOLLOUT } else { 0 })
|
||||||
|
)
|
||||||
|
// vfmt on
|
||||||
|
ev.data.fd = fd
|
||||||
|
|
||||||
|
if events & picoev_del != 0 {
|
||||||
|
// nothing to do
|
||||||
|
} else if events & picoev_readwrite == 0 {
|
||||||
|
// delete the file if it exists
|
||||||
|
epoll_ret := C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_DEL, fd, &ev)
|
||||||
|
|
||||||
|
// check error
|
||||||
|
assert epoll_ret == 0
|
||||||
|
} else {
|
||||||
|
// change settings to 0
|
||||||
|
mut epoll_ret := C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_MOD, fd, &ev)
|
||||||
|
if epoll_ret != 0 {
|
||||||
|
// if the file is not present we want to add it
|
||||||
|
assert C.errno == C.ENOENT
|
||||||
|
epoll_ret = C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_ADD, fd, &ev)
|
||||||
|
|
||||||
|
// check error
|
||||||
|
assert epoll_ret == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to u32?
|
||||||
|
target.events = u32(events)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) poll_once(max_wait int) int {
|
||||||
|
nevents := C.epoll_wait(pv.loop.epoll_fd, &pv.loop.events, max_fds, max_wait * 1000)
|
||||||
|
|
||||||
|
if nevents == -1 {
|
||||||
|
// timeout has occurred
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < nevents; i++ {
|
||||||
|
mut event := pv.loop.events[i]
|
||||||
|
target := unsafe { pv.file_descriptors[event.data.fd] }
|
||||||
|
unsafe {
|
||||||
|
assert event.data.fd < max_fds
|
||||||
|
}
|
||||||
|
if pv.loop.id == target.loop_id && target.events & picoev_readwrite != 0 {
|
||||||
|
// vfmt off
|
||||||
|
read_events := (
|
||||||
|
(if event.events & u32(C.EPOLLIN) != 0 { picoev_read } else { 0 })
|
||||||
|
|
|
||||||
|
(if event.events & u32(C.EPOLLOUT) != 0 { picoev_write } else { 0 })
|
||||||
|
)
|
||||||
|
// vfmt on
|
||||||
|
if read_events != 0 {
|
||||||
|
// do callback!
|
||||||
|
unsafe { target.cb(event.data.fd, read_events, &pv) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// defer epoll delete
|
||||||
|
event.events = 0
|
||||||
|
unsafe {
|
||||||
|
C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_DEL, event.data.fd, &event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
203
vlib/picoev/loop_macos.c.v
Normal file
203
vlib/picoev/loop_macos.c.v
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
module picoev
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/event.h>
|
||||||
|
|
||||||
|
fn C.kevent(int, changelist voidptr, nchanges int, eventlist voidptr, nevents int, timout &C.timespec) int
|
||||||
|
fn C.kqueue() int
|
||||||
|
fn C.EV_SET(kev voidptr, ident int, filter i16, flags u16, fflags u32, data voidptr, udata voidptr)
|
||||||
|
|
||||||
|
pub struct C.kevent {
|
||||||
|
pub mut:
|
||||||
|
ident int
|
||||||
|
// uintptr_t
|
||||||
|
filter i16
|
||||||
|
flags u16
|
||||||
|
fflags u32
|
||||||
|
data voidptr
|
||||||
|
// intptr_t
|
||||||
|
udata voidptr
|
||||||
|
}
|
||||||
|
|
||||||
|
[heap]
|
||||||
|
pub struct KqueueLoop {
|
||||||
|
mut:
|
||||||
|
id int
|
||||||
|
now i64
|
||||||
|
kq_id int
|
||||||
|
// -1 if not changed
|
||||||
|
changed_fds int
|
||||||
|
events [1024]C.kevent
|
||||||
|
changelist [256]C.kevent
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoopType = KqueueLoop
|
||||||
|
|
||||||
|
// create_kqueue_loop creates a new kernel event queue with loop_id=`id`
|
||||||
|
pub fn create_kqueue_loop(id int) !&KqueueLoop {
|
||||||
|
mut loop := &KqueueLoop{
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.kq_id = C.kqueue()
|
||||||
|
if loop.kq_id == -1 {
|
||||||
|
return error('could not create kqueue loop!')
|
||||||
|
}
|
||||||
|
loop.changed_fds = -1
|
||||||
|
return loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// ev_set sets a new `kevent` with file descriptor `index`
|
||||||
|
[inline]
|
||||||
|
pub fn (mut pv Picoev) ev_set(index int, operation int, events int) {
|
||||||
|
// vfmt off
|
||||||
|
filter := i16(
|
||||||
|
(if events & picoev_read != 0 { C.EVFILT_READ } else { 0 })
|
||||||
|
|
|
||||||
|
(if events & picoev_write != 0 { C.EVFILT_WRITE } else { 0 })
|
||||||
|
)
|
||||||
|
// vfmt on
|
||||||
|
C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0,
|
||||||
|
0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// backend_build uses the lower 8 bits to store the old events and the higher 8
|
||||||
|
// bits to store the next file descriptor in `Target.backend`
|
||||||
|
[inline]
|
||||||
|
fn backend_build(next_fd int, events u32) int {
|
||||||
|
return int((u32(next_fd) << 8) | (events & 0xff))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the lower 8 bits
|
||||||
|
[inline]
|
||||||
|
fn backend_get_old_events(backend int) int {
|
||||||
|
return backend & 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the higher 8 bits
|
||||||
|
[inline]
|
||||||
|
fn backend_get_next_fd(backend int) int {
|
||||||
|
return backend >> 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply pending processes all changes for the file descriptors and updates `loop.changelist`
|
||||||
|
// if `aplly_all` is `true` the changes are immediately applied
|
||||||
|
fn (mut pv Picoev) apply_pending_changes(apply_all bool) int {
|
||||||
|
mut total, mut nevents := 0, 0
|
||||||
|
|
||||||
|
for pv.loop.changed_fds != -1 {
|
||||||
|
mut target := pv.file_descriptors[pv.loop.changed_fds]
|
||||||
|
old_events := backend_get_old_events(target.backend)
|
||||||
|
if target.events != old_events {
|
||||||
|
// events have been changed
|
||||||
|
if old_events != 0 {
|
||||||
|
pv.ev_set(total, C.EV_DISABLE, old_events)
|
||||||
|
total++
|
||||||
|
}
|
||||||
|
if target.events != 0 {
|
||||||
|
pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events))
|
||||||
|
total++
|
||||||
|
}
|
||||||
|
// Apply the changes if the total changes exceed the changelist size
|
||||||
|
if total + 1 >= pv.loop.changelist.len {
|
||||||
|
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL,
|
||||||
|
0, C.NULL)
|
||||||
|
assert nevents == 0
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pv.loop.changed_fds = backend_get_next_fd(target.backend)
|
||||||
|
target.backend = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if apply_all && total != 0 {
|
||||||
|
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL)
|
||||||
|
assert nevents == 0
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) update_events(fd int, events int) int {
|
||||||
|
// check if fd is in range
|
||||||
|
assert fd < max_fds
|
||||||
|
|
||||||
|
mut target := pv.file_descriptors[fd]
|
||||||
|
|
||||||
|
// initialize if adding the fd
|
||||||
|
if events & picoev_add != 0 {
|
||||||
|
target.backend = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// return if nothing to do
|
||||||
|
if (events == picoev_del && target.backend == -1)
|
||||||
|
|| (events != picoev_del && events & picoev_readwrite == target.events) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to changed list if not yet being done
|
||||||
|
if target.backend == -1 {
|
||||||
|
target.backend = backend_build(pv.loop.changed_fds, target.events)
|
||||||
|
pv.loop.changed_fds = fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// update events
|
||||||
|
target.events = u32(events & picoev_readwrite)
|
||||||
|
// apply immediately if is a DELETE
|
||||||
|
if events & picoev_del != 0 {
|
||||||
|
pv.apply_pending_changes(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) poll_once(max_wait int) int {
|
||||||
|
ts := C.timespec{
|
||||||
|
tv_sec: max_wait
|
||||||
|
tv_nsec: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mut total, mut nevents := 0, 0
|
||||||
|
// apply changes later when the callback is called.
|
||||||
|
total = pv.apply_pending_changes(false)
|
||||||
|
|
||||||
|
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, pv.loop.events.len,
|
||||||
|
&ts)
|
||||||
|
if nevents == -1 {
|
||||||
|
// the errors we can only rescue
|
||||||
|
assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < nevents; i++ {
|
||||||
|
event := pv.loop.events[i]
|
||||||
|
target := pv.file_descriptors[event.ident]
|
||||||
|
|
||||||
|
// changelist errors are fatal
|
||||||
|
assert event.flags & C.EV_ERROR == 0
|
||||||
|
|
||||||
|
if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 {
|
||||||
|
read_events := match int(event.filter) {
|
||||||
|
C.EVFILT_READ {
|
||||||
|
picoev_read
|
||||||
|
}
|
||||||
|
C.EVFILT_WRITE {
|
||||||
|
picoev_write
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do callback!
|
||||||
|
unsafe { target.cb(target.fd, read_events, &pv) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -1,53 +1,31 @@
|
||||||
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
|
|
||||||
// Use of this source code is governed by an MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
module picoev
|
module picoev
|
||||||
|
|
||||||
import net
|
|
||||||
import picohttpparser
|
import picohttpparser
|
||||||
|
import time
|
||||||
|
|
||||||
#include <errno.h>
|
pub const (
|
||||||
#include <netinet/tcp.h>
|
max_fds = 1024
|
||||||
#include <signal.h>
|
max_queue = 4096
|
||||||
#flag -I @VEXEROOT/thirdparty/picoev
|
|
||||||
#flag @VEXEROOT/thirdparty/picoev/picoev.o
|
|
||||||
#include "src/picoev.h"
|
|
||||||
|
|
||||||
[typedef]
|
// events
|
||||||
struct C.picoev_loop {}
|
picoev_read = 1
|
||||||
|
picoev_write = 2
|
||||||
fn C.picoev_del(&C.picoev_loop, int) int
|
picoev_timeout = 4
|
||||||
|
picoev_add = 0x40000000
|
||||||
fn C.picoev_set_timeout(&C.picoev_loop, int, int)
|
picoev_del = 0x20000000
|
||||||
|
picoev_readwrite = 3 // 1 xor 2
|
||||||
// fn C.picoev_handler(loop &C.picoev_loop, fd int, revents int, cb_arg voidptr)
|
|
||||||
// TODO: (sponge) update to C.picoev_handler with C type def update
|
|
||||||
type PicoevHandler = fn (loop &C.picoev_loop, fd int, revents int, context voidptr)
|
|
||||||
|
|
||||||
fn C.picoev_add(&C.picoev_loop, int, int, int, &PicoevHandler, voidptr) int
|
|
||||||
|
|
||||||
fn C.picoev_init(int) int
|
|
||||||
|
|
||||||
fn C.picoev_create_loop(int) &C.picoev_loop
|
|
||||||
|
|
||||||
fn C.picoev_loop_once(&C.picoev_loop, int) int
|
|
||||||
|
|
||||||
fn C.picoev_destroy_loop(&C.picoev_loop) int
|
|
||||||
|
|
||||||
fn C.picoev_deinit() int
|
|
||||||
|
|
||||||
const (
|
|
||||||
max_fds = 1024
|
|
||||||
max_timeout = 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
enum Event {
|
// Target is a data representation of everything that needs to be associated with a single
|
||||||
read = C.PICOEV_READ
|
// file descriptor (connection)
|
||||||
write = C.PICOEV_WRITE
|
pub struct Target {
|
||||||
timeout = C.PICOEV_TIMEOUT
|
pub mut:
|
||||||
add = C.PICOEV_ADD
|
fd int
|
||||||
del = C.PICOEV_DEL
|
loop_id int = -1
|
||||||
readwrite = C.PICOEV_READWRITE
|
events u32
|
||||||
|
cb fn (int, int, voidptr)
|
||||||
|
// used internally by the kqueue implementation
|
||||||
|
backend int
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -62,137 +40,230 @@ pub:
|
||||||
max_write int = 8192
|
max_write int = 8192
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Picoev {
|
[heap]
|
||||||
loop &C.picoev_loop = unsafe { nil }
|
pub struct Picoev {
|
||||||
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
|
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
|
||||||
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError)
|
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
|
||||||
user_data voidptr
|
user_data voidptr = unsafe { nil }
|
||||||
|
|
||||||
timeout_secs int
|
timeout_secs int
|
||||||
max_headers int
|
max_headers int = 100
|
||||||
max_read int
|
max_read int = 4096
|
||||||
max_write int
|
max_write int = 8192
|
||||||
mut:
|
mut:
|
||||||
date &u8 = unsafe { nil }
|
loop &LoopType = unsafe { nil }
|
||||||
buf &u8 = unsafe { nil }
|
file_descriptors [max_fds]&Target
|
||||||
idx [1024]int
|
timeouts map[int]i64
|
||||||
out &u8 = unsafe { nil }
|
num_loops int
|
||||||
|
|
||||||
|
buf &u8 = unsafe { nil }
|
||||||
|
idx [1024]int
|
||||||
|
out &u8 = unsafe { nil }
|
||||||
|
|
||||||
|
date string
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
// init fills the `file_descriptors` array
|
||||||
fn setup_sock(fd int) ! {
|
pub fn (mut pv Picoev) init() {
|
||||||
flag := 1
|
assert picoev.max_fds > 0
|
||||||
if C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_NODELAY, &flag, sizeof(int)) < 0 {
|
|
||||||
return error('setup_sock.setup_sock failed')
|
pv.num_loops = 0
|
||||||
|
|
||||||
|
for i in 0 .. picoev.max_fds {
|
||||||
|
pv.file_descriptors[i] = &Target{}
|
||||||
}
|
}
|
||||||
$if freebsd {
|
}
|
||||||
if C.fcntl(fd, C.F_SETFL, C.SOCK_NONBLOCK) != 0 {
|
|
||||||
return error('fcntl failed')
|
// add adds a file descriptor to the loop
|
||||||
}
|
[direct_array_access]
|
||||||
} $else {
|
pub fn (mut pv Picoev) add(fd int, events int, timeout int, cb voidptr) int {
|
||||||
if C.fcntl(fd, C.F_SETFL, C.O_NONBLOCK) != 0 {
|
assert fd < picoev.max_fds
|
||||||
return error('fcntl failed')
|
|
||||||
|
mut target := pv.file_descriptors[fd]
|
||||||
|
target.fd = fd
|
||||||
|
target.cb = cb
|
||||||
|
target.loop_id = pv.loop.id
|
||||||
|
target.events = 0
|
||||||
|
|
||||||
|
if pv.update_events(fd, events | picoev.picoev_add) != 0 {
|
||||||
|
pv.del(fd)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// update timeout
|
||||||
|
pv.set_timeout(fd, timeout)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// del removes a file descriptor from the loop
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut pv Picoev) del(fd int) int {
|
||||||
|
assert fd < picoev.max_fds
|
||||||
|
mut target := pv.file_descriptors[fd]
|
||||||
|
|
||||||
|
$if trace_fd ? {
|
||||||
|
eprintln('delete ${fd}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if pv.update_events(fd, picoev.picoev_del) != 0 {
|
||||||
|
target.loop_id = -1
|
||||||
|
target.fd = 0
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
pv.set_timeout(fd, 0)
|
||||||
|
target.loop_id = -1
|
||||||
|
target.fd = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pv Picoev) loop_once(max_wait int) int {
|
||||||
|
pv.loop.now = get_time()
|
||||||
|
|
||||||
|
if pv.poll_once(max_wait) != 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if max_wait != 0 {
|
||||||
|
pv.loop.now = get_time()
|
||||||
|
}
|
||||||
|
|
||||||
|
pv.handle_timeout()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_timeout sets the timeout in seconds for a file descriptor. If a timeout occurs
|
||||||
|
// the file descriptors target callback is called with a timeout event
|
||||||
|
[direct_array_access; inline]
|
||||||
|
fn (mut pv Picoev) set_timeout(fd int, secs int) {
|
||||||
|
assert fd < picoev.max_fds
|
||||||
|
if secs != 0 {
|
||||||
|
pv.timeouts[fd] = pv.loop.now + secs
|
||||||
|
} else {
|
||||||
|
pv.timeouts.delete(fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle_timeout loops over all file descriptors and removes them from the loop
|
||||||
|
// if they are timed out. Also the file descriptors target callback is called with a
|
||||||
|
// timeout event
|
||||||
|
[direct_array_access; inline]
|
||||||
|
fn (mut pv Picoev) handle_timeout() {
|
||||||
|
for fd, timeout in pv.timeouts {
|
||||||
|
if timeout <= pv.loop.now {
|
||||||
|
target := pv.file_descriptors[fd]
|
||||||
|
assert target.loop_id == pv.loop.id
|
||||||
|
|
||||||
|
pv.timeouts.delete(fd)
|
||||||
|
unsafe { target.cb(fd, picoev.picoev_timeout, &pv) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
// accept_callback accepts a new connection from `listen_fd` and adds it to the loop
|
||||||
fn close_conn(loop &C.picoev_loop, fd int) {
|
fn accept_callback(listen_fd int, events int, cb_arg voidptr) {
|
||||||
C.picoev_del(voidptr(loop), fd)
|
mut pv := unsafe { &Picoev(cb_arg) }
|
||||||
C.close(fd)
|
newfd := accept(listen_fd)
|
||||||
}
|
if newfd >= picoev.max_fds {
|
||||||
|
// should never happen
|
||||||
[inline]
|
close_socket(newfd)
|
||||||
fn req_read(fd int, b &u8, max_len int, idx int) int {
|
|
||||||
unsafe {
|
|
||||||
return C.read(fd, b + idx, max_len - idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rw_callback(loop &C.picoev_loop, fd int, events int, context voidptr) {
|
|
||||||
mut p := unsafe { &Picoev(context) }
|
|
||||||
defer {
|
|
||||||
p.idx[fd] = 0
|
|
||||||
}
|
|
||||||
if (events & int(Event.timeout)) != 0 {
|
|
||||||
close_conn(loop, fd)
|
|
||||||
return
|
return
|
||||||
} else if (events & int(Event.read)) != 0 {
|
}
|
||||||
C.picoev_set_timeout(voidptr(loop), fd, p.timeout_secs)
|
|
||||||
|
|
||||||
// Request init
|
$if trace_fd ? {
|
||||||
mut buf := p.buf
|
eprintln('accept ${newfd}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if newfd != -1 {
|
||||||
|
setup_sock(newfd) or {
|
||||||
|
eprintln('setup_sock failed, fd: ${newfd}, listen_fd: ${listen_fd}, err: ${err.code()}')
|
||||||
|
pv.err_cb(pv.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
|
||||||
|
err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pv.add(newfd, picoev.picoev_read, pv.timeout_secs, raw_callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close_conn closes the socket `fd` and removes it from the loop
|
||||||
|
[inline]
|
||||||
|
pub fn (mut pv Picoev) close_conn(fd int) {
|
||||||
|
pv.del(fd)
|
||||||
|
close_socket(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn raw_callback(fd int, events int, context voidptr) {
|
||||||
|
mut pv := unsafe { &Picoev(context) }
|
||||||
|
defer {
|
||||||
|
pv.idx[fd] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if events & picoev.picoev_timeout != 0 {
|
||||||
|
$if trace_fd ? {
|
||||||
|
eprintln('timeout ${fd}')
|
||||||
|
}
|
||||||
|
pv.close_conn(fd)
|
||||||
|
return
|
||||||
|
} else if events & picoev.picoev_read != 0 {
|
||||||
|
pv.set_timeout(fd, pv.timeout_secs)
|
||||||
|
|
||||||
|
mut buf := pv.buf
|
||||||
unsafe {
|
unsafe {
|
||||||
buf += fd * p.max_read // pointer magic
|
buf += fd * pv.max_read // pointer magic
|
||||||
}
|
}
|
||||||
mut req := picohttpparser.Request{}
|
mut req := picohttpparser.Request{}
|
||||||
|
|
||||||
// Response init
|
// Response init
|
||||||
mut out := p.out
|
mut out := pv.out
|
||||||
unsafe {
|
unsafe {
|
||||||
out += fd * p.max_write // pointer magic
|
out += fd * pv.max_write // pointer magic
|
||||||
}
|
}
|
||||||
mut res := picohttpparser.Response{
|
mut res := picohttpparser.Response{
|
||||||
fd: fd
|
fd: fd
|
||||||
date: p.date
|
|
||||||
buf_start: out
|
buf_start: out
|
||||||
buf: out
|
buf: out
|
||||||
|
date: pv.date.str
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Request parsing loop
|
// Request parsing loop
|
||||||
r := req_read(fd, buf, p.max_read, p.idx[fd]) // Get data from socket
|
r := req_read(fd, buf, pv.max_read, pv.idx[fd]) // Get data from socket
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
// connection closed by peer
|
// connection closed by peer
|
||||||
close_conn(loop, fd)
|
pv.close_conn(fd)
|
||||||
return
|
return
|
||||||
} else if r == -1 {
|
} else if r == -1 {
|
||||||
// error
|
if fatal_socket_error(fd) == false {
|
||||||
if C.errno == C.EAGAIN {
|
|
||||||
// try again later
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if C.errno == C.EWOULDBLOCK {
|
|
||||||
// try again later
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// fatal error
|
// fatal error
|
||||||
close_conn(loop, fd)
|
pv.close_conn(fd)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.idx[fd] += r
|
pv.idx[fd] += r
|
||||||
|
|
||||||
mut s := unsafe { tos(buf, p.idx[fd]) }
|
mut s := unsafe { tos(buf, pv.idx[fd]) }
|
||||||
pret := req.parse_request(s, p.max_headers) // Parse request via picohttpparser
|
pret := req.parse_request(s) or {
|
||||||
|
// Parse error
|
||||||
|
pv.err_cb(pv.user_data, req, mut &res, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if pret > 0 { // Success
|
if pret > 0 { // Success
|
||||||
break
|
break
|
||||||
} else if pret == -1 { // Parse error
|
|
||||||
p.err_cb(p.user_data, req, mut &res, error('ParseError'))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert pret == -2
|
assert pret == -2
|
||||||
// request is incomplete, continue the loop
|
// request is incomplete, continue the loop
|
||||||
if p.idx[fd] == sizeof(buf) {
|
if pv.idx[fd] == sizeof(buf) {
|
||||||
p.err_cb(p.user_data, req, mut &res, error('RequestIsTooLongError'))
|
pv.err_cb(pv.user_data, req, mut &res, error('RequestIsTooLongError'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback (should call .end() itself)
|
// Callback (should call .end() itself)
|
||||||
p.cb(p.user_data, req, mut &res)
|
pv.cb(pv.user_data, req, mut &res)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept_callback(loop &C.picoev_loop, fd int, events int, cb_arg voidptr) {
|
|
||||||
mut p := unsafe { &Picoev(cb_arg) }
|
|
||||||
newfd := C.accept(fd, 0, 0)
|
|
||||||
if newfd != -1 {
|
|
||||||
setup_sock(newfd) or {
|
|
||||||
p.err_cb(p.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
C.picoev_add(voidptr(loop), newfd, int(Event.read), p.timeout_secs, rw_callback,
|
|
||||||
cb_arg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,42 +272,12 @@ fn default_err_cb(data voidptr, req picohttpparser.Request, mut res picohttppars
|
||||||
res.end()
|
res.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new creates a `Picoev` struct and initializes the main loop
|
||||||
pub fn new(config Config) &Picoev {
|
pub fn new(config Config) &Picoev {
|
||||||
fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0)
|
listen_fd := listen(config)
|
||||||
assert fd != -1
|
|
||||||
|
|
||||||
// Setting flags for socket
|
|
||||||
flag := 1
|
|
||||||
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEADDR, &flag, sizeof(int)) == 0
|
|
||||||
$if linux {
|
|
||||||
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEPORT, &flag, sizeof(int)) == 0
|
|
||||||
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_QUICKACK, &flag, sizeof(int)) == 0
|
|
||||||
timeout := 10
|
|
||||||
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_DEFER_ACCEPT, &timeout, sizeof(int)) == 0
|
|
||||||
queue_len := 4096
|
|
||||||
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_FASTOPEN, &queue_len, sizeof(int)) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting addr
|
|
||||||
mut addr := C.sockaddr_in{
|
|
||||||
sin_family: u8(C.AF_INET)
|
|
||||||
sin_port: C.htons(config.port)
|
|
||||||
sin_addr: C.htonl(C.INADDR_ANY)
|
|
||||||
}
|
|
||||||
size := sizeof(C.sockaddr_in)
|
|
||||||
bind_res := C.bind(fd, voidptr(unsafe { &net.Addr(&addr) }), size)
|
|
||||||
assert bind_res == 0
|
|
||||||
listen_res := C.listen(fd, C.SOMAXCONN)
|
|
||||||
assert listen_res == 0
|
|
||||||
setup_sock(fd) or {
|
|
||||||
config.err_cb(config.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
|
|
||||||
C.picoev_init(picoev.max_fds)
|
|
||||||
loop := C.picoev_create_loop(picoev.max_timeout)
|
|
||||||
mut pv := &Picoev{
|
mut pv := &Picoev{
|
||||||
loop: loop
|
num_loops: 1
|
||||||
cb: config.cb
|
cb: config.cb
|
||||||
err_cb: config.err_cb
|
err_cb: config.err_cb
|
||||||
user_data: config.user_data
|
user_data: config.user_data
|
||||||
|
@ -244,25 +285,45 @@ pub fn new(config Config) &Picoev {
|
||||||
max_headers: config.max_headers
|
max_headers: config.max_headers
|
||||||
max_read: config.max_read
|
max_read: config.max_read
|
||||||
max_write: config.max_write
|
max_write: config.max_write
|
||||||
date: &u8(C.get_date())
|
|
||||||
buf: unsafe { malloc_noscan(picoev.max_fds * config.max_read + 1) }
|
buf: unsafe { malloc_noscan(picoev.max_fds * config.max_read + 1) }
|
||||||
out: unsafe { malloc_noscan(picoev.max_fds * config.max_write + 1) }
|
out: unsafe { malloc_noscan(picoev.max_fds * config.max_write + 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
C.picoev_add(voidptr(loop), fd, int(Event.read), 0, accept_callback, pv)
|
// epoll for linux
|
||||||
spawn update_date(mut pv)
|
// kqueue for macos and bsd
|
||||||
|
// select for windows and others
|
||||||
|
$if linux {
|
||||||
|
pv.loop = create_epoll_loop(0) or { panic(err) }
|
||||||
|
} $else $if freebsd || macos {
|
||||||
|
pv.loop = create_kqueue_loop(0) or { panic(err) }
|
||||||
|
} $else {
|
||||||
|
pv.loop = create_select_loop(0) or { panic(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pv.init()
|
||||||
|
|
||||||
|
pv.add(listen_fd, picoev.picoev_read, 0, accept_callback)
|
||||||
return pv
|
return pv
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (p Picoev) serve() {
|
// serve starts the Picoev server
|
||||||
|
pub fn (mut pv Picoev) serve() {
|
||||||
|
spawn update_date(mut pv)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
C.picoev_loop_once(p.loop, 1)
|
pv.loop_once(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_date(mut p Picoev) {
|
// update_date updates `date` on `pv` every second.
|
||||||
|
fn update_date(mut pv Picoev) {
|
||||||
for {
|
for {
|
||||||
p.date = &u8(C.get_date())
|
// get GMT (UTC) time for the HTTP Date header
|
||||||
C.usleep(1000000)
|
gmt := time.utc()
|
||||||
|
mut date := gmt.strftime('---, %d --- %Y %H:%M:%S GMT')
|
||||||
|
date = date.replace_once('---', gmt.weekday_str())
|
||||||
|
date = date.replace_once('---', gmt.smonth())
|
||||||
|
pv.date = date
|
||||||
|
time.sleep(time.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
143
vlib/picoev/socket_util.c.v
Normal file
143
vlib/picoev/socket_util.c.v
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
module picoev
|
||||||
|
|
||||||
|
import net
|
||||||
|
import picohttpparser
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
$if windows {
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
} $else $if freebsd || macos {
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
} $else {
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn get_time() i64 {
|
||||||
|
// time.now() is slow
|
||||||
|
return i64(C.time(C.NULL))
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn accept(fd int) int {
|
||||||
|
return C.accept(fd, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn close_socket(fd int) {
|
||||||
|
$if trace_fd ? {
|
||||||
|
eprintln('close ${fd}')
|
||||||
|
}
|
||||||
|
|
||||||
|
$if windows {
|
||||||
|
C.closesocket(fd)
|
||||||
|
} $else {
|
||||||
|
C.close(fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn setup_sock(fd int) ! {
|
||||||
|
flag := 1
|
||||||
|
|
||||||
|
if C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_NODELAY, &flag, sizeof(int)) < 0 {
|
||||||
|
return error('setup_sock.setup_sock failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
$if freebsd {
|
||||||
|
if C.fcntl(fd, C.F_SETFL, C.SOCK_NONBLOCK) != 0 {
|
||||||
|
return error('fcntl failed')
|
||||||
|
}
|
||||||
|
} $else $if windows {
|
||||||
|
non_blocking_mode := u32(1)
|
||||||
|
if C.ioctlsocket(fd, C.FIONBIO, &non_blocking_mode) == C.SOCKET_ERROR {
|
||||||
|
return error('icotlsocket failed')
|
||||||
|
}
|
||||||
|
} $else {
|
||||||
|
// linux and macos
|
||||||
|
if C.fcntl(fd, C.F_SETFL, C.O_NONBLOCK) != 0 {
|
||||||
|
return error('fcntl failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn req_read(fd int, b &u8, max_len int, idx int) int {
|
||||||
|
// use `recv` instead of `read` for windows compatibility
|
||||||
|
unsafe {
|
||||||
|
return C.recv(fd, b + idx, max_len - idx, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fatal_socket_error(fd int) bool {
|
||||||
|
if C.errno == C.EAGAIN {
|
||||||
|
// try again later
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
$if windows {
|
||||||
|
if C.errno == C.WSAEWOULDBLOCK {
|
||||||
|
// try again later
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} $else {
|
||||||
|
if C.errno == C.EWOULDBLOCK {
|
||||||
|
// try again later
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$if trace_fd ? {
|
||||||
|
eprintln('fatal error ${fd}: ${C.errno}')
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen creates a listening tcp socket and returns its file decriptor
|
||||||
|
fn listen(config Config) int {
|
||||||
|
// not using the `net` modules sockets, because not all socket options are defined
|
||||||
|
fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0)
|
||||||
|
assert fd != -1
|
||||||
|
|
||||||
|
$if trace_fd ? {
|
||||||
|
eprintln('listen: ${fd}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting flags for socket
|
||||||
|
flag := 1
|
||||||
|
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEADDR, &flag, sizeof(int)) == 0
|
||||||
|
|
||||||
|
$if linux {
|
||||||
|
// epoll socket options
|
||||||
|
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEPORT, &flag, sizeof(int)) == 0
|
||||||
|
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_QUICKACK, &flag, sizeof(int)) == 0
|
||||||
|
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_DEFER_ACCEPT, &config.timeout_secs,
|
||||||
|
sizeof(int)) == 0
|
||||||
|
queue_len := max_queue
|
||||||
|
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_FASTOPEN, &queue_len, sizeof(int)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// addr settings
|
||||||
|
mut addr := C.sockaddr_in{
|
||||||
|
sin_family: u8(C.AF_INET)
|
||||||
|
sin_port: C.htons(config.port)
|
||||||
|
sin_addr: C.htonl(C.INADDR_ANY)
|
||||||
|
}
|
||||||
|
size := sizeof(C.sockaddr_in)
|
||||||
|
bind_res := C.bind(fd, voidptr(unsafe { &net.Addr(&addr) }), size)
|
||||||
|
assert bind_res == 0
|
||||||
|
|
||||||
|
listen_res := C.listen(fd, C.SOMAXCONN)
|
||||||
|
assert listen_res == 0
|
||||||
|
setup_sock(fd) or {
|
||||||
|
config.err_cb(config.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
## Description:
|
## Description:
|
||||||
|
|
||||||
`picohttpparser` is a thin wrapper over [picohttpparser](https://github.com/h2o/picohttpparser),
|
`picohttpparser` is V implementation of
|
||||||
|
[picohttpparser](https://github.com/h2o/picohttpparser),
|
||||||
which in turn is "a tiny, primitive, fast HTTP request/response parser."
|
which in turn is "a tiny, primitive, fast HTTP request/response parser."
|
||||||
|
|
|
@ -1,20 +1,76 @@
|
||||||
module picohttpparser
|
module picohttpparser
|
||||||
|
|
||||||
[inline; unsafe]
|
const (
|
||||||
fn cpy(dst &u8, src &u8, len int) int {
|
// vfmt off
|
||||||
unsafe { C.memcpy(dst, src, len) }
|
g_digits_lut = [
|
||||||
return len
|
`0`,`0`,`0`,`1`,`0`,`2`,`0`,`3`,`0`,`4`,`0`,`5`,`0`,`6`,`0`,`7`,`0`,`8`,`0`,`9`,
|
||||||
}
|
`1`,`0`,`1`,`1`,`1`,`2`,`1`,`3`,`1`,`4`,`1`,`5`,`1`,`6`,`1`,`7`,`1`,`8`,`1`,`9`,
|
||||||
|
`2`,`0`,`2`,`1`,`2`,`2`,`2`,`3`,`2`,`4`,`2`,`5`,`2`,`6`,`2`,`7`,`2`,`8`,`2`,`9`,
|
||||||
|
`3`,`0`,`3`,`1`,`3`,`2`,`3`,`3`,`3`,`4`,`3`,`5`,`3`,`6`,`3`,`7`,`3`,`8`,`3`,`9`,
|
||||||
|
`4`,`0`,`4`,`1`,`4`,`2`,`4`,`3`,`4`,`4`,`4`,`5`,`4`,`6`,`4`,`7`,`4`,`8`,`4`,`9`,
|
||||||
|
`5`,`0`,`5`,`1`,`5`,`2`,`5`,`3`,`5`,`4`,`5`,`5`,`5`,`6`,`5`,`7`,`5`,`8`,`5`,`9`,
|
||||||
|
`6`,`0`,`6`,`1`,`6`,`2`,`6`,`3`,`6`,`4`,`6`,`5`,`6`,`6`,`6`,`7`,`6`,`8`,`6`,`9`,
|
||||||
|
`7`,`0`,`7`,`1`,`7`,`2`,`7`,`3`,`7`,`4`,`7`,`5`,`7`,`6`,`7`,`7`,`7`,`8`,`7`,`9`,
|
||||||
|
`8`,`0`,`8`,`1`,`8`,`2`,`8`,`3`,`8`,`4`,`8`,`5`,`8`,`6`,`8`,`7`,`8`,`8`,`8`,`9`,
|
||||||
|
`9`,`0`,`9`,`1`,`9`,`2`,`9`,`3`,`9`,`4`,`9`,`5`,`9`,`6`,`9`,`7`,`9`,`8`,`9`,`9`
|
||||||
|
]
|
||||||
|
// vfmt on
|
||||||
|
)
|
||||||
|
|
||||||
[inline]
|
// u64toa converts `value` to an ascii string and stores it at `buf_start`
|
||||||
pub fn cmp(dst string, src string) bool {
|
// then it returns the length of the ascii string (branch lookup table implementation)
|
||||||
if dst.len != src.len {
|
[direct_array_access; inline]
|
||||||
return false
|
fn u64toa(buf_start &u8, value u64) !int {
|
||||||
|
mut buf := unsafe { buf_start }
|
||||||
|
// set maximum length to 100MB
|
||||||
|
if value >= 100_000_000 {
|
||||||
|
return error('Maximum size of 100MB exceeded!')
|
||||||
}
|
}
|
||||||
return unsafe { C.memcmp(dst.str, src.str, src.len) == 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
[inline]
|
v := u32(value)
|
||||||
pub fn cmpn(dst string, src string, n int) bool {
|
if v < 10_000 {
|
||||||
return unsafe { C.memcmp(dst.str, src.str, n) == 0 }
|
d1 := u32((v / 100) << 1)
|
||||||
|
d2 := u32((v % 100) << 1)
|
||||||
|
unsafe {
|
||||||
|
if v >= 1000 {
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d1]
|
||||||
|
}
|
||||||
|
if v >= 100 {
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d1 + 1]
|
||||||
|
}
|
||||||
|
if v >= 10 {
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d2]
|
||||||
|
}
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d2 + 1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b := v / 10_000
|
||||||
|
c := v % 10_000
|
||||||
|
|
||||||
|
d1 := u32((b / 100) << 1)
|
||||||
|
d2 := u32((b % 100) << 1)
|
||||||
|
|
||||||
|
d3 := u32((c / 100) << 1)
|
||||||
|
d4 := u32((c % 100) << 1)
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if value >= 10_000_000 {
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d1]
|
||||||
|
}
|
||||||
|
if value >= 1_000_000 {
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d1 + 1]
|
||||||
|
}
|
||||||
|
if value >= 100_000 {
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d2]
|
||||||
|
}
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d2 + 1]
|
||||||
|
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d3]
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d3 + 1]
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d4]
|
||||||
|
*buf++ = picohttpparser.g_digits_lut[d4 + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unsafe { buf - buf_start }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,489 @@
|
||||||
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
|
|
||||||
// Use of this source code is governed by an MIT license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
module picohttpparser
|
module picohttpparser
|
||||||
|
|
||||||
#flag -I @VEXEROOT/thirdparty/picohttpparser
|
// NOTE: picohttpparser is designed for speed. Please do some benchmarks when
|
||||||
#flag @VEXEROOT/thirdparty/picohttpparser/picohttpparser.o
|
// you change something in this file
|
||||||
|
|
||||||
#include "picohttpparser.h"
|
const (
|
||||||
|
// token_char_map contains all allowed characters in HTTP headers
|
||||||
|
token_char_map = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||||
|
'\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0' +
|
||||||
|
'\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1' +
|
||||||
|
'\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0' +
|
||||||
|
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||||
|
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||||
|
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||||
|
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
|
||||||
|
)
|
||||||
|
|
||||||
struct C.phr_header {
|
fn (mut r Request) phr_parse_request_path(buf_start &u8, buf_end &u8, mut pret Pret) {
|
||||||
pub:
|
mut buf := unsafe { buf_start + 0 }
|
||||||
name &char
|
|
||||||
name_len int
|
// ADVANCE_TOKEN
|
||||||
value &char
|
method := advance_token(buf, buf_end, mut pret)
|
||||||
value_len int
|
if pret.ret < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
$if trace_parse ? {
|
||||||
|
eprintln('method: ${method}')
|
||||||
|
}
|
||||||
|
// skip spaces
|
||||||
|
for {
|
||||||
|
unsafe { buf++ }
|
||||||
|
if *buf != ` ` {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path := advance_token(buf, buf_end, mut pret)
|
||||||
|
if pret.ret < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$if trace_parse ? {
|
||||||
|
eprintln('path: ${path}')
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
// skip spaces
|
||||||
|
for {
|
||||||
|
unsafe { buf++ }
|
||||||
|
if *buf != ` ` {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// validate
|
||||||
|
if method.len == 0 || path.len == 0 {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: invalid method or path'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.method = method
|
||||||
|
r.path = path
|
||||||
|
|
||||||
|
pret.ret = unsafe { buf - buf_start }
|
||||||
}
|
}
|
||||||
|
|
||||||
type PPchar = &&char
|
fn (mut r Request) phr_parse_request_path_pipeline(buf_start &u8, buf_end &u8, mut pret Pret) {
|
||||||
|
mut buf := unsafe { buf_start }
|
||||||
|
method := advance_token2(buf, buf_end, mut pret)
|
||||||
|
if pret.ret < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
path := advance_token2(buf, buf_end, mut pret)
|
||||||
|
if pret.ret < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
// validate
|
||||||
|
if method.len == 0 || path.len == 0 {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: invalid method or path'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.method = method
|
||||||
|
r.path = path
|
||||||
|
|
||||||
struct C.phr_header_t {}
|
for buf < buf_end {
|
||||||
|
unsafe { buf++ }
|
||||||
|
// check if following 4 characters are '\r\n\r\n' indicating a new request line
|
||||||
|
if unsafe { *(&u32(buf)) == 0x0a0d0a0d } {
|
||||||
|
unsafe {
|
||||||
|
buf += 4
|
||||||
|
}
|
||||||
|
pret.ret = unsafe { buf - buf_start }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn C.phr_parse_request(buf &char, len usize, method PPchar, method_len &usize, path PPchar, path_len &usize, minor_version &int, headers &C.phr_header, num_headers &usize, last_len usize) int
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: no request found'
|
||||||
|
}
|
||||||
|
|
||||||
fn C.phr_parse_response(buf &char, len usize, minor_version &int, status &int, msg PPchar, msg_len &usize, headers &C.phr_header, num_headers &usize, last_len usize) int
|
fn (mut r Request) phr_parse_request(buf_start &u8, buf_end &u8, mut pret Pret) &u8 {
|
||||||
|
// make copy of `buf_start` that can be mutated
|
||||||
|
mut buf := unsafe { buf_start }
|
||||||
|
|
||||||
fn C.phr_parse_headers(buf &char, len usize, headers &C.phr_header, num_headers &usize, last_len usize) int
|
// skip first empty line (some clients add CRLF after POST content)
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if *buf == `\r` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
// EXPECT_CHAR
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if *buf != `\n` {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn C.phr_parse_request_path(buf_start &char, len usize, method PPchar, method_len &usize, path PPchar, path_len &usize) int
|
// parse request line
|
||||||
fn C.phr_parse_request_path_pipeline(buf_start &char, len usize, method PPchar, method_len &usize, path PPchar, path_len &usize) int
|
r.phr_parse_request_path(buf, buf_end, mut pret)
|
||||||
fn C.get_date() &char
|
if pret.ret < 0 {
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
minor_version := parse_http_version(buf, buf_end, mut pret)
|
||||||
|
if pret.ret < 0 {
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
$if trace_parse ? {
|
||||||
|
eprintln('minor_version: ${minor_version}')
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if *buf == `\r` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
// EXPECT_CHAR
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if *buf != `\n` {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
} else if *buf == `\n` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
} else {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expecting "\r\n" after HTTP version'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
// static inline int u64toa(char* buf, uint64_t value) {
|
return r.parse_headers(buf, buf_end, mut pret)
|
||||||
fn C.u64toa(buffer &char, value u64) int
|
}
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn (mut r Request) parse_headers(buf_start &u8, buf_end &u8, mut pret Pret) &u8 {
|
||||||
|
mut buf := unsafe { buf_start }
|
||||||
|
|
||||||
|
mut i := 0
|
||||||
|
|
||||||
|
for i = r.num_headers; i < max_headers; i++ {
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if *buf == `\r` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
// EXPECT_CHAR
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if *buf != `\n` {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
|
||||||
|
break
|
||||||
|
} else if *buf == `\n` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(*buf == ` ` || *buf == `\t`) {
|
||||||
|
name_start := buf
|
||||||
|
// parsing name, but do not discard SP before colon, see
|
||||||
|
// http://www.mozilla.org/security/announce/2006/mfsa2006-33.html
|
||||||
|
for *buf != `:` {
|
||||||
|
// check if the current character is allowed in an HTTP header
|
||||||
|
if picohttpparser.token_char_map[*buf] == 0 {
|
||||||
|
$if trace_parse ? {
|
||||||
|
eprintln('invalid character! ${*buf}')
|
||||||
|
}
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: invalid character in header "${*buf}"'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name_len := unsafe { buf - name_start }
|
||||||
|
if name_len == 0 {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: invalid header name'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
r.headers[i].name = unsafe { tos(name_start, name_len) }
|
||||||
|
|
||||||
|
unsafe { buf++ }
|
||||||
|
for { // CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
if !(*buf == ` ` || *buf == `\t`) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.headers[i].name = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
mut value_len := get_token_length_to_eol(buf, buf_end, mut pret)
|
||||||
|
if pret.ret < 0 {
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: strip characters
|
||||||
|
value_end := unsafe { buf + value_len }
|
||||||
|
for value_end != buf {
|
||||||
|
c := unsafe { *(value_end - 1) }
|
||||||
|
if !(c == ` ` || c == `\t`) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
unsafe { value_end-- }
|
||||||
|
}
|
||||||
|
|
||||||
|
r.headers[i].value = unsafe { tos(buf, value_end - buf) }
|
||||||
|
r.num_headers++
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
buf += pret.ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == max_headers {
|
||||||
|
// too many headers
|
||||||
|
eprintln('Too many headers!')
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: too many headers!'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
pret.ret = unsafe { buf - buf_start }
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_complete checks if an http request is done
|
||||||
|
fn is_complete(buf_start &u8, buf_end &u8, last_len int, mut pret Pret) &u8 {
|
||||||
|
mut ret_cnt := 0
|
||||||
|
// get the last 3 characters of the request buffer
|
||||||
|
buf := if last_len < 3 { buf_start } else { unsafe { buf_start + last_len - 3 } }
|
||||||
|
|
||||||
|
for {
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
// We expect a line of an http request to end with '\r\n'
|
||||||
|
if *buf == `\r` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
// EXPECT_CHAR_NO_CHECK
|
||||||
|
if *buf != `\n` {
|
||||||
|
// no '\n' after '\r' indicates a parse error
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
|
||||||
|
ret_cnt++
|
||||||
|
} else if *buf == `\n` {
|
||||||
|
unsafe { buf++ }
|
||||||
|
ret_cnt++
|
||||||
|
} else {
|
||||||
|
// other character
|
||||||
|
unsafe { buf++ }
|
||||||
|
ret_cnt = 0
|
||||||
|
}
|
||||||
|
if ret_cnt == 2 {
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pret.ret = -2
|
||||||
|
return unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_http_version(buf_start &u8, buf_end &u8, mut pret Pret) int {
|
||||||
|
// we want at least [HTTP/1.<two chars>] to try to parse
|
||||||
|
if unsafe { buf_end - buf_start } < 9 {
|
||||||
|
pret.ret = -2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if unsafe { tos(buf_start, 7) != 'HTTP/1.' } {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: picohttpparser only supports HTTP/1.x'
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// PARSE_INT
|
||||||
|
c := unsafe { *(buf_start + 7) }
|
||||||
|
if c < `0` || c > `9` {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: invalid HTTP version'
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
pret.ret = 8
|
||||||
|
return int(c - `0`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_length_to_eol(buf_start &u8, buf_end &u8, mut pret Pret) int {
|
||||||
|
mut buf := unsafe { buf_start }
|
||||||
|
mut token_len := 0
|
||||||
|
|
||||||
|
// find non-printable char within the next 8 bytes
|
||||||
|
// HOT code: (TODO: should be manually inlined)
|
||||||
|
for _likely_(unsafe { buf_end - buf >= 8 }) {
|
||||||
|
for _ in 0 .. 8 {
|
||||||
|
if _unlikely_(!is_printable_ascii(*buf)) {
|
||||||
|
// non printable
|
||||||
|
unsafe {
|
||||||
|
goto non_printable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
continue
|
||||||
|
|
||||||
|
non_printable:
|
||||||
|
// allow space and horizontal tab
|
||||||
|
if _likely_(*buf < ` ` && *buf != 9) || _unlikely_(*buf == 127) {
|
||||||
|
// found clear the line (CTL)
|
||||||
|
unsafe {
|
||||||
|
goto found_ctl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remaining characters
|
||||||
|
for {
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if _likely_(*buf < ` ` && *buf != 9) || _unlikely_(*buf == 127) {
|
||||||
|
// found clear the line (CTL)
|
||||||
|
unsafe {
|
||||||
|
goto found_ctl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
}
|
||||||
|
|
||||||
|
found_ctl:
|
||||||
|
if _likely_(*buf == `\r`) {
|
||||||
|
unsafe { buf++ }
|
||||||
|
// EXPECT_CHAR
|
||||||
|
if buf == buf_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if *buf != `\n` {
|
||||||
|
// no '\n' after '\r' indicates a parse error
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
token_len = unsafe { buf - 2 - buf_start }
|
||||||
|
} else if *buf == `\n` {
|
||||||
|
token_len = unsafe { buf - buf_start }
|
||||||
|
unsafe { buf++ }
|
||||||
|
} else {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: expecting "\r\n" after header'
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if token_len == 0 {
|
||||||
|
pret.ret = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pret.ret = unsafe { buf - buf_start }
|
||||||
|
return token_len
|
||||||
|
}
|
||||||
|
|
||||||
|
// following functions are #define in the C version, but inline here for better readability
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn advance_token(tok_start &u8, tok_end &u8, mut pret Pret) string {
|
||||||
|
mut buf := unsafe { tok_start }
|
||||||
|
for *buf != ` ` {
|
||||||
|
if _unlikely_(!is_printable_ascii(*buf)) {
|
||||||
|
if *buf < ` ` || *buf == 127 {
|
||||||
|
pret.ret = -1
|
||||||
|
pret.err = 'error parsing request: invalid character "${*buf}"'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { buf++ }
|
||||||
|
// CHECK_EOF
|
||||||
|
if buf == tok_end {
|
||||||
|
pret.ret = -2
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pret.ret = unsafe { buf - tok_start }
|
||||||
|
return unsafe { tos(tok_start, pret.ret) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance_token2 is a less safe version of advance_token
|
||||||
|
[inline]
|
||||||
|
fn advance_token2(tok_start &u8, tok_end &u8, mut pret Pret) string {
|
||||||
|
mut len := 0
|
||||||
|
mut i := 0
|
||||||
|
for {
|
||||||
|
if unsafe { *(tok_start + i) == ` ` } {
|
||||||
|
len = i
|
||||||
|
for unsafe { *(tok_start + i) == ` ` } {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
pret.ret = i
|
||||||
|
return unsafe { tos(tok_start, len) }
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn is_printable_ascii(c u8) bool {
|
||||||
|
return u32(c - 32) < 95
|
||||||
|
}
|
||||||
|
|
|
@ -1,66 +1,99 @@
|
||||||
module picohttpparser
|
module picohttpparser
|
||||||
|
|
||||||
|
const (
|
||||||
|
max_headers = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
pub struct Header {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
mut:
|
mut:
|
||||||
prev_len int
|
prev_len int
|
||||||
pub mut:
|
pub mut:
|
||||||
method string
|
method string
|
||||||
path string
|
path string
|
||||||
headers [100]C.phr_header
|
headers [max_headers]Header
|
||||||
num_headers u64
|
num_headers int
|
||||||
body string
|
body string
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
// Pret contains the nr of bytes read, a negative number indicates an error
|
||||||
pub fn (mut r Request) parse_request(s string, max_headers int) int {
|
struct Pret {
|
||||||
method_len := usize(0)
|
pub mut:
|
||||||
path_len := usize(0)
|
err string
|
||||||
minor_version := 0
|
// -1 indicates a parse error and -2 means the request is parsed
|
||||||
num_headers := usize(max_headers)
|
ret int
|
||||||
|
}
|
||||||
|
|
||||||
pret := C.phr_parse_request(&char(s.str), s.len, voidptr(&r.method.str), &method_len,
|
// parse_request parses a raw HTTP request and returns the number of bytes read.
|
||||||
voidptr(&r.path.str), &path_len, &minor_version, &r.headers[0], &num_headers,
|
// -1 indicates a parse error and -2 means the request is parsed
|
||||||
r.prev_len)
|
[inline]
|
||||||
if pret > 0 {
|
pub fn (mut r Request) parse_request(s string) !int {
|
||||||
unsafe {
|
mut buf := s.str
|
||||||
r.method = tos(r.method.str, int(method_len))
|
buf_end := unsafe { s.str + s.len }
|
||||||
r.path = tos(r.path.str, int(path_len))
|
|
||||||
|
mut pret := Pret{}
|
||||||
|
// if prev_len != 0, check if the request is complete
|
||||||
|
// (a fast countermeasure against slowloris)
|
||||||
|
if r.prev_len != 0 && unsafe { is_complete(buf, buf_end, r.prev_len, mut pret) == nil } {
|
||||||
|
if pret.ret == -1 {
|
||||||
|
return error(pret.err)
|
||||||
}
|
}
|
||||||
r.num_headers = u64(num_headers)
|
return pret.ret
|
||||||
}
|
}
|
||||||
r.body = unsafe { (&s.str[pret]).vstring_literal_with_len(s.len - pret) }
|
|
||||||
|
buf = r.phr_parse_request(buf, buf_end, mut pret)
|
||||||
|
if pret.ret == -1 {
|
||||||
|
return error(pret.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if unsafe { buf == nil } {
|
||||||
|
return pret.ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pret.ret = unsafe { buf - s.str }
|
||||||
|
|
||||||
|
r.body = unsafe { (&s.str[pret.ret]).vstring_literal_with_len(s.len - pret.ret) }
|
||||||
r.prev_len = s.len
|
r.prev_len = s.len
|
||||||
return pret
|
|
||||||
|
// return nr of bytes
|
||||||
|
return pret.ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse_request_path sets the `path` and `method` fields
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut r Request) parse_request_path(s string) int {
|
pub fn (mut r Request) parse_request_path(s string) !int {
|
||||||
method_len := usize(0)
|
mut buf := s.str
|
||||||
path_len := usize(0)
|
buf_end := unsafe { s.str + s.len }
|
||||||
|
|
||||||
pret := C.phr_parse_request_path(&char(s.str), s.len, voidptr(&r.method.str), &method_len,
|
mut pret := Pret{}
|
||||||
voidptr(&r.path.str), &path_len)
|
r.phr_parse_request_path(buf, buf_end, mut pret)
|
||||||
if pret > 0 {
|
if pret.ret == -1 {
|
||||||
unsafe {
|
return error(pret.err)
|
||||||
r.method = tos(r.method.str, int(method_len))
|
|
||||||
r.path = tos(r.path.str, int(path_len))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return pret
|
|
||||||
|
return pret.ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse_request_path_pipeline can parse the `path` and `method` of HTTP/1.1 pipelines.
|
||||||
|
// Call it again to parse the next request
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut r Request) parse_request_path_pipeline(s string) int {
|
pub fn (mut r Request) parse_request_path_pipeline(s string) !int {
|
||||||
method_len := usize(0)
|
mut buf := unsafe { s.str + r.prev_len }
|
||||||
path_len := usize(0)
|
buf_end := unsafe { s.str + s.len }
|
||||||
|
|
||||||
pret := C.phr_parse_request_path_pipeline(&char(s.str), s.len, voidptr(&r.method.str),
|
mut pret := Pret{}
|
||||||
&method_len, voidptr(&r.path.str), &path_len)
|
r.phr_parse_request_path_pipeline(buf, buf_end, mut pret)
|
||||||
if pret > 0 {
|
if pret.ret == -1 {
|
||||||
unsafe {
|
return error(pret.err)
|
||||||
r.method = tos(r.method.str, int(method_len))
|
|
||||||
r.path = tos(r.path.str, int(path_len))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return pret
|
|
||||||
|
if pret.ret > 0 {
|
||||||
|
r.prev_len = pret.ret
|
||||||
|
}
|
||||||
|
return pret.ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@ pub fn (mut r Response) header(k string, v string) &Response {
|
||||||
pub fn (mut r Response) header_date() &Response {
|
pub fn (mut r Response) header_date() &Response {
|
||||||
r.write_string('Date: ')
|
r.write_string('Date: ')
|
||||||
unsafe {
|
unsafe {
|
||||||
r.buf += cpy(r.buf, r.date, 29)
|
C.memcpy(r.buf, r.date, 29)
|
||||||
|
r.buf += 29
|
||||||
}
|
}
|
||||||
r.write_string('\r\n')
|
r.write_string('\r\n')
|
||||||
return unsafe { r }
|
return unsafe { r }
|
||||||
|
@ -78,7 +79,7 @@ pub fn (mut r Response) json() &Response {
|
||||||
pub fn (mut r Response) body(body string) {
|
pub fn (mut r Response) body(body string) {
|
||||||
r.write_string('Content-Length: ')
|
r.write_string('Content-Length: ')
|
||||||
unsafe {
|
unsafe {
|
||||||
r.buf += C.u64toa(&char(r.buf), body.len)
|
r.buf += u64toa(r.buf, u64(body.len)) or { panic(err) }
|
||||||
}
|
}
|
||||||
r.write_string('\r\n\r\n')
|
r.write_string('\r\n\r\n')
|
||||||
r.write_string(body)
|
r.write_string(body)
|
||||||
|
@ -107,7 +108,8 @@ pub fn (mut r Response) raw(response string) {
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut r Response) end() int {
|
pub fn (mut r Response) end() int {
|
||||||
n := int(i64(r.buf) - i64(r.buf_start))
|
n := int(i64(r.buf) - i64(r.buf_start))
|
||||||
if C.write(r.fd, r.buf_start, n) != n {
|
// use send instead of write for windows compatibility
|
||||||
|
if C.send(r.fd, r.buf_start, n, 0) != n {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -23,21 +23,26 @@ enum Char_parse_state {
|
||||||
}
|
}
|
||||||
|
|
||||||
// v_printf prints a sprintf-like formated `string` to the terminal.
|
// v_printf prints a sprintf-like formated `string` to the terminal.
|
||||||
[deprecated: 'use string interpolation instead']
|
// The format string `str` can be constructed at runtime.
|
||||||
fn v_printf(str string, pt ...voidptr) {
|
// Note, that this function is unsafe.
|
||||||
print(v_sprintf(str, ...pt))
|
// In most cases, you are better off using V's string interpolation,
|
||||||
|
// when your format string is known at compile time.
|
||||||
|
[unsafe]
|
||||||
|
pub fn v_printf(str string, pt ...voidptr) {
|
||||||
|
print(unsafe { v_sprintf(str, ...pt) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// v_sprintf returns a sprintf-like formated `string`.
|
// v_sprintf returns a sprintf-like formated `string`.
|
||||||
//
|
// The format string `str` can be constructed at runtime.
|
||||||
|
// Note, that this function is unsafe.
|
||||||
|
// In most cases, you are better off using V's string interpolation,
|
||||||
|
// when your format string is known at compile time.
|
||||||
// Example:
|
// Example:
|
||||||
// ```v
|
// ```v
|
||||||
// x := 3.141516
|
// x := 3.141516
|
||||||
// assert strconv.v_sprintf('aaa %G', x) == 'aaa 3.141516'
|
// assert strconv.v_sprintf('aaa %G', x) == 'aaa 3.141516'
|
||||||
// ```
|
// ```
|
||||||
[deprecated: 'use string interpolation instead']
|
[direct_array_access; manualfree; unsafe]
|
||||||
[deprecated_after: '2023-06-30']
|
|
||||||
[direct_array_access; manualfree]
|
|
||||||
pub fn v_sprintf(str string, pt ...voidptr) string {
|
pub fn v_sprintf(str string, pt ...voidptr) string {
|
||||||
mut res := strings.new_builder(pt.len * 16)
|
mut res := strings.new_builder(pt.len * 16)
|
||||||
defer {
|
defer {
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn event(e &tui.Event, x voidptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame(x voidptr) {
|
fn frame(x voidptr) {
|
||||||
mut app := &App(x)
|
mut app := unsafe { &App(x) }
|
||||||
|
|
||||||
app.tui.clear()
|
app.tui.clear()
|
||||||
app.tui.set_bg_color(r: 63, g: 81, b: 181)
|
app.tui.set_bg_color(r: 63, g: 81, b: 181)
|
||||||
|
|
|
@ -86,6 +86,10 @@ pub enum FormatDelimiter {
|
||||||
no_delimiter
|
no_delimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn Time.new(t Time) Time {
|
||||||
|
return new_time(t)
|
||||||
|
}
|
||||||
|
|
||||||
// smonth returns month name abbreviation.
|
// smonth returns month name abbreviation.
|
||||||
pub fn (t Time) smonth() string {
|
pub fn (t Time) smonth() string {
|
||||||
if t.month <= 0 || t.month > 12 {
|
if t.month <= 0 || t.month > 12 {
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub fn (t Time) local() Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
// in most systems, these are __quad_t, which is an i64
|
// in most systems, these are __quad_t, which is an i64
|
||||||
struct C.timespec {
|
pub struct C.timespec {
|
||||||
mut:
|
mut:
|
||||||
tv_sec i64
|
tv_sec i64
|
||||||
tv_nsec i64
|
tv_nsec i64
|
||||||
|
|
|
@ -2013,6 +2013,17 @@ pub fn (expr Expr) is_expr() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn (expr Expr) get_pure_type() Type {
|
||||||
|
match expr {
|
||||||
|
BoolLiteral { return bool_type }
|
||||||
|
CharLiteral { return char_type }
|
||||||
|
FloatLiteral { return f64_type }
|
||||||
|
StringLiteral { return string_type }
|
||||||
|
IntegerLiteral { return i64_type }
|
||||||
|
else { return void_type }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (expr Expr) is_pure_literal() bool {
|
pub fn (expr Expr) is_pure_literal() bool {
|
||||||
return match expr {
|
return match expr {
|
||||||
BoolLiteral, CharLiteral, FloatLiteral, StringLiteral, IntegerLiteral { true }
|
BoolLiteral, CharLiteral, FloatLiteral, StringLiteral, IntegerLiteral { true }
|
||||||
|
|
|
@ -695,8 +695,7 @@ pub fn (t &Table) type_kind(typ Type) Kind {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (t &Table) type_is_for_pointer_arithmetic(typ Type) bool {
|
pub fn (t &Table) type_is_for_pointer_arithmetic(typ Type) bool {
|
||||||
typ_sym := t.sym(typ)
|
if t.sym(typ).kind == .struct_ {
|
||||||
if typ_sym.kind == .struct_ {
|
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
return typ.is_any_kind_of_pointer() || typ.is_int_valptr()
|
return typ.is_any_kind_of_pointer() || typ.is_int_valptr()
|
||||||
|
@ -881,9 +880,32 @@ pub fn (t &TypeSymbol) sumtype_info() SumType {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (t &TypeSymbol) is_heap() bool {
|
pub fn (t &TypeSymbol) is_heap() bool {
|
||||||
if t.kind == .struct_ {
|
if t.info is Struct {
|
||||||
info := t.info as Struct
|
return t.info.is_heap
|
||||||
return info.is_heap
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (t &ArrayFixed) is_compatible(t2 ArrayFixed) bool {
|
||||||
|
return t.size == t2.size && t.elem_type == t2.elem_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (t &TypeSymbol) is_array_fixed() bool {
|
||||||
|
if t.info is ArrayFixed {
|
||||||
|
return true
|
||||||
|
} else if t.info is Alias {
|
||||||
|
return global_table.final_sym(t.info.parent_type).is_array_fixed()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (t &TypeSymbol) is_array_fixed_ret() bool {
|
||||||
|
if t.info is ArrayFixed {
|
||||||
|
return t.info.is_fn_ret
|
||||||
|
} else if t.info is Alias {
|
||||||
|
return global_table.final_sym(t.info.parent_type).is_array_fixed_ret()
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1154,10 +1176,10 @@ pub fn (t &Table) type_to_str(typ Type) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// type name in code (for builtin)
|
// type name in code (for builtin)
|
||||||
pub fn (mytable &Table) type_to_code(t Type) string {
|
pub fn (t &Table) type_to_code(typ Type) string {
|
||||||
match t {
|
match typ {
|
||||||
ast.int_literal_type, ast.float_literal_type { return mytable.sym(t).kind.str() }
|
ast.int_literal_type, ast.float_literal_type { return t.sym(typ).kind.str() }
|
||||||
else { return mytable.type_to_str_using_aliases(t, map[string]string{}) }
|
else { return t.type_to_str_using_aliases(typ, map[string]string{}) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,12 +82,44 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
|
||||||
if !(b.pref.is_test || b.pref.is_run || b.pref.is_crun) {
|
if !(b.pref.is_test || b.pref.is_run || b.pref.is_crun) {
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
compiled_file := os.real_path(b.pref.out_name)
|
mut compiled_file := b.pref.out_name
|
||||||
|
if b.pref.backend == .wasm && !compiled_file.ends_with('.wasm') {
|
||||||
|
compiled_file += '.wasm'
|
||||||
|
}
|
||||||
|
compiled_file = os.real_path(compiled_file)
|
||||||
|
|
||||||
|
mut run_args := []string{cap: b.pref.run_args.len + 1}
|
||||||
|
|
||||||
run_file := if b.pref.backend.is_js() {
|
run_file := if b.pref.backend.is_js() {
|
||||||
node_basename := $if windows { 'node.exe' } $else { 'node' }
|
node_basename := $if windows { 'node.exe' } $else { 'node' }
|
||||||
os.find_abs_path_of_executable(node_basename) or {
|
os.find_abs_path_of_executable(node_basename) or {
|
||||||
panic('Could not find `${node_basename}` in system path. Do you have Node.js installed?')
|
panic('Could not find `${node_basename}` in system path. Do you have Node.js installed?')
|
||||||
}
|
}
|
||||||
|
} else if b.pref.backend == .wasm {
|
||||||
|
mut actual_run := ['wasmer', 'wasmtime', 'wavm', 'wasm3']
|
||||||
|
mut actual_rf := ''
|
||||||
|
|
||||||
|
// -autofree bug
|
||||||
|
// error: cannot convert 'struct string' to 'struct _option_string'
|
||||||
|
// mut actual_rf := ?string(none)
|
||||||
|
|
||||||
|
for runtime in actual_run {
|
||||||
|
basename := $if windows { runtime + '.exe' } $else { runtime }
|
||||||
|
|
||||||
|
if rf := os.find_abs_path_of_executable(basename) {
|
||||||
|
if basename == 'wavm' {
|
||||||
|
run_args << 'run'
|
||||||
|
}
|
||||||
|
actual_rf = rf
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual_rf == '' {
|
||||||
|
panic('Could not find `wasmer`, `wasmtime`, `wavm`, or `wasm3` in system path. Do you have any installed?')
|
||||||
|
}
|
||||||
|
|
||||||
|
actual_rf
|
||||||
} else if b.pref.backend == .golang {
|
} else if b.pref.backend == .golang {
|
||||||
go_basename := $if windows { 'go.exe' } $else { 'go' }
|
go_basename := $if windows { 'go.exe' } $else { 'go' }
|
||||||
os.find_abs_path_of_executable(go_basename) or {
|
os.find_abs_path_of_executable(go_basename) or {
|
||||||
|
@ -96,8 +128,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
|
||||||
} else {
|
} else {
|
||||||
compiled_file
|
compiled_file
|
||||||
}
|
}
|
||||||
mut run_args := []string{cap: b.pref.run_args.len + 1}
|
if b.pref.backend.is_js() || b.pref.backend == .wasm {
|
||||||
if b.pref.backend.is_js() {
|
|
||||||
run_args << compiled_file
|
run_args << compiled_file
|
||||||
} else if b.pref.backend == .golang {
|
} else if b.pref.backend == .golang {
|
||||||
run_args << ['run', compiled_file]
|
run_args << ['run', compiled_file]
|
||||||
|
|
|
@ -227,7 +227,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Make sure the variable is mutable
|
// Make sure the variable is mutable
|
||||||
c.fail_if_immutable(left)
|
c.fail_if_immutable(mut left)
|
||||||
|
|
||||||
if !is_blank_ident && !left_type.has_flag(.option) && right_type.has_flag(.option) {
|
if !is_blank_ident && !left_type.has_flag(.option) && right_type.has_flag(.option) {
|
||||||
c.error('cannot assign an Option value to a non-option variable', right.pos())
|
c.error('cannot assign an Option value to a non-option variable', right.pos())
|
||||||
|
@ -664,11 +664,28 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !is_blank_ident && !left.is_auto_deref_var() && !right.is_auto_deref_var()
|
if !is_blank_ident && right_sym.kind != .placeholder && left_sym.kind != .interface_
|
||||||
&& right_sym.kind != .placeholder && left_sym.kind != .interface_
|
|
||||||
&& !right_type.has_flag(.generic) && !left_type.has_flag(.generic) {
|
&& !right_type.has_flag(.generic) && !left_type.has_flag(.generic) {
|
||||||
// Dual sides check (compatibility check)
|
// Dual sides check (compatibility check)
|
||||||
c.check_expected(right_type_unwrapped, left_type_unwrapped) or {
|
c.check_expected(right_type_unwrapped, left_type_unwrapped) or {
|
||||||
|
// allow literal values to auto deref var (e.g.`for mut v in values { v = 1.0 }`)
|
||||||
|
if left.is_auto_deref_var() || right.is_auto_deref_var() {
|
||||||
|
left_deref := if left.is_auto_deref_var() {
|
||||||
|
left_type.deref()
|
||||||
|
} else {
|
||||||
|
left_type
|
||||||
|
}
|
||||||
|
right_deref := if right.is_pure_literal() {
|
||||||
|
right.get_pure_type()
|
||||||
|
} else if right.is_auto_deref_var() {
|
||||||
|
right_type.deref()
|
||||||
|
} else {
|
||||||
|
right_type
|
||||||
|
}
|
||||||
|
if c.check_types(left_deref, right_deref) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
// allow for ptr += 2
|
// allow for ptr += 2
|
||||||
if left_type_unwrapped.is_ptr() && right_type_unwrapped.is_int()
|
if left_type_unwrapped.is_ptr() && right_type_unwrapped.is_int()
|
||||||
&& node.op in [.plus_assign, .minus_assign] {
|
&& node.op in [.plus_assign, .minus_assign] {
|
||||||
|
|
|
@ -661,21 +661,21 @@ fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, ifac
|
||||||
|
|
||||||
// returns name and position of variable that needs write lock
|
// returns name and position of variable that needs write lock
|
||||||
// also sets `is_changed` to true (TODO update the name to reflect this?)
|
// also sets `is_changed` to true (TODO update the name to reflect this?)
|
||||||
fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) {
|
||||||
mut to_lock := '' // name of variable that needs lock
|
mut to_lock := '' // name of variable that needs lock
|
||||||
mut pos := token.Pos{} // and its position
|
mut pos := token.Pos{} // and its position
|
||||||
mut explicit_lock_needed := false
|
mut explicit_lock_needed := false
|
||||||
mut expr := unsafe { expr_ }
|
|
||||||
match mut expr {
|
match mut expr {
|
||||||
ast.CastExpr {
|
ast.CastExpr {
|
||||||
// TODO
|
// TODO
|
||||||
return '', expr.pos
|
return '', expr.pos
|
||||||
}
|
}
|
||||||
ast.ComptimeSelector {
|
ast.ComptimeSelector {
|
||||||
|
mut expr_left := expr.left
|
||||||
if mut expr.left is ast.Ident {
|
if mut expr.left is ast.Ident {
|
||||||
if mut expr.left.obj is ast.Var {
|
if mut expr.left.obj is ast.Var {
|
||||||
if expr.left.obj.ct_type_var != .generic_param {
|
if expr.left.obj.ct_type_var != .generic_param {
|
||||||
c.fail_if_immutable(expr.left)
|
c.fail_if_immutable(mut expr_left)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -685,8 +685,13 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
if mut expr.obj is ast.Var {
|
if mut expr.obj is ast.Var {
|
||||||
if !expr.obj.is_mut && !c.pref.translated && !c.file.is_translated
|
if !expr.obj.is_mut && !c.pref.translated && !c.file.is_translated
|
||||||
&& !c.inside_unsafe {
|
&& !c.inside_unsafe {
|
||||||
c.error('`${expr.name}` is immutable, declare it with `mut` to make it mutable',
|
if c.inside_anon_fn {
|
||||||
expr.pos)
|
c.error('the closure copy of `${expr.name}` is immutable, declare it with `mut` to make it mutable',
|
||||||
|
expr.pos)
|
||||||
|
} else {
|
||||||
|
c.error('`${expr.name}` is immutable, declare it with `mut` to make it mutable',
|
||||||
|
expr.pos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
expr.obj.is_changed = true
|
expr.obj.is_changed = true
|
||||||
if expr.obj.typ.share() == .shared_t {
|
if expr.obj.typ.share() == .shared_t {
|
||||||
|
@ -735,21 +740,21 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
c.error('you have to create a handle and `lock` it to modify `shared` ${kind} element',
|
c.error('you have to create a handle and `lock` it to modify `shared` ${kind} element',
|
||||||
expr.left.pos().extend(expr.pos))
|
expr.left.pos().extend(expr.pos))
|
||||||
}
|
}
|
||||||
to_lock, pos = c.fail_if_immutable(expr.left)
|
to_lock, pos = c.fail_if_immutable(mut expr.left)
|
||||||
}
|
}
|
||||||
ast.ParExpr {
|
ast.ParExpr {
|
||||||
to_lock, pos = c.fail_if_immutable(expr.expr)
|
to_lock, pos = c.fail_if_immutable(mut expr.expr)
|
||||||
}
|
}
|
||||||
ast.PrefixExpr {
|
ast.PrefixExpr {
|
||||||
if expr.op == .mul && expr.right is ast.Ident {
|
if expr.op == .mul && expr.right is ast.Ident {
|
||||||
// Do not fail if dereference is immutable:
|
// Do not fail if dereference is immutable:
|
||||||
// `*x = foo()` doesn't modify `x`
|
// `*x = foo()` doesn't modify `x`
|
||||||
} else {
|
} else {
|
||||||
to_lock, pos = c.fail_if_immutable(expr.right)
|
to_lock, pos = c.fail_if_immutable(mut expr.right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.PostfixExpr {
|
ast.PostfixExpr {
|
||||||
to_lock, pos = c.fail_if_immutable(expr.expr)
|
to_lock, pos = c.fail_if_immutable(mut expr.expr)
|
||||||
}
|
}
|
||||||
ast.SelectorExpr {
|
ast.SelectorExpr {
|
||||||
if expr.expr_type == 0 {
|
if expr.expr_type == 0 {
|
||||||
|
@ -792,7 +797,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
c.error('field `${expr.field_name}` of struct `${type_str}` is immutable',
|
c.error('field `${expr.field_name}` of struct `${type_str}` is immutable',
|
||||||
expr.pos)
|
expr.pos)
|
||||||
}
|
}
|
||||||
to_lock, pos = c.fail_if_immutable(expr.expr)
|
to_lock, pos = c.fail_if_immutable(mut expr.expr)
|
||||||
}
|
}
|
||||||
if to_lock != '' {
|
if to_lock != '' {
|
||||||
// No automatic lock for struct access
|
// No automatic lock for struct access
|
||||||
|
@ -812,7 +817,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
expr.pos)
|
expr.pos)
|
||||||
return '', expr.pos
|
return '', expr.pos
|
||||||
}
|
}
|
||||||
c.fail_if_immutable(expr.expr)
|
c.fail_if_immutable(mut expr.expr)
|
||||||
}
|
}
|
||||||
.sum_type {
|
.sum_type {
|
||||||
sumtype_info := typ_sym.info as ast.SumType
|
sumtype_info := typ_sym.info as ast.SumType
|
||||||
|
@ -827,7 +832,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
expr.pos)
|
expr.pos)
|
||||||
return '', expr.pos
|
return '', expr.pos
|
||||||
}
|
}
|
||||||
c.fail_if_immutable(expr.expr)
|
c.fail_if_immutable(mut expr.expr)
|
||||||
}
|
}
|
||||||
.array, .string {
|
.array, .string {
|
||||||
// should only happen in `builtin` and unsafe blocks
|
// should only happen in `builtin` and unsafe blocks
|
||||||
|
@ -838,7 +843,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.aggregate, .placeholder {
|
.aggregate, .placeholder {
|
||||||
c.fail_if_immutable(expr.expr)
|
c.fail_if_immutable(mut expr.expr)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
c.error('unexpected symbol `${typ_sym.kind}`', expr.pos)
|
c.error('unexpected symbol `${typ_sym.kind}`', expr.pos)
|
||||||
|
@ -849,7 +854,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
ast.CallExpr {
|
ast.CallExpr {
|
||||||
// TODO: should only work for builtin method
|
// TODO: should only work for builtin method
|
||||||
if expr.name == 'slice' {
|
if expr.name == 'slice' {
|
||||||
to_lock, pos = c.fail_if_immutable(expr.left)
|
to_lock, pos = c.fail_if_immutable(mut expr.left)
|
||||||
if to_lock != '' {
|
if to_lock != '' {
|
||||||
// No automatic lock for array slicing (yet(?))
|
// No automatic lock for array slicing (yet(?))
|
||||||
explicit_lock_needed = true
|
explicit_lock_needed = true
|
||||||
|
@ -2153,7 +2158,7 @@ fn (mut c Checker) asm_ios(mut ios []ast.AsmIO, mut scope ast.Scope, output bool
|
||||||
for mut io in ios {
|
for mut io in ios {
|
||||||
typ := c.expr(mut io.expr)
|
typ := c.expr(mut io.expr)
|
||||||
if output {
|
if output {
|
||||||
c.fail_if_immutable(io.expr)
|
c.fail_if_immutable(mut io.expr)
|
||||||
}
|
}
|
||||||
if io.alias != '' {
|
if io.alias != '' {
|
||||||
aliases << io.alias
|
aliases << io.alias
|
||||||
|
@ -2943,9 +2948,9 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type {
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if from_type == ast.voidptr_type_idx && !c.inside_unsafe {
|
if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated
|
||||||
// TODO make this an error
|
&& !c.file.is_translated {
|
||||||
c.warn('cannot cast voidptr to a struct outside `unsafe`', node.pos)
|
c.error('cannot cast voidptr to a struct outside `unsafe`', node.pos)
|
||||||
}
|
}
|
||||||
if !from_type.is_int() && final_from_sym.kind != .enum_
|
if !from_type.is_int() && final_from_sym.kind != .enum_
|
||||||
&& !from_type.is_any_kind_of_pointer() {
|
&& !from_type.is_any_kind_of_pointer() {
|
||||||
|
@ -3825,7 +3830,7 @@ fn (c &Checker) has_return(stmts []ast.Stmt) ?bool {
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut c Checker) is_comptime_var(node ast.Expr) bool {
|
pub fn (mut c Checker) is_comptime_var(node ast.Expr) bool {
|
||||||
return node is ast.Ident && node.info is ast.IdentVar && node.kind == .variable
|
return node is ast.Ident && node.info is ast.IdentVar && node.kind == .variable
|
||||||
&& ((node as ast.Ident).obj as ast.Var).ct_type_var != .no_comptime
|
&& (node.obj as ast.Var).ct_type_var != .no_comptime
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) {
|
fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) {
|
||||||
|
|
|
@ -409,6 +409,11 @@ fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
|
||||||
expecting_interface_map := map_value_sym.kind == .interface_
|
expecting_interface_map := map_value_sym.kind == .interface_
|
||||||
//
|
//
|
||||||
mut same_key_type := true
|
mut same_key_type := true
|
||||||
|
|
||||||
|
if node.keys.len == 1 && val0_type == ast.none_type {
|
||||||
|
c.error('map value cannot be only `none`', node.vals[0].pos())
|
||||||
|
}
|
||||||
|
|
||||||
for i, mut key in node.keys {
|
for i, mut key in node.keys {
|
||||||
if i == 0 && !use_expected_type {
|
if i == 0 && !use_expected_type {
|
||||||
continue
|
continue
|
||||||
|
@ -445,6 +450,9 @@ fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
|
||||||
c.error('invalid map value: ${msg}', val.pos())
|
c.error('invalid map value: ${msg}', val.pos())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if val_type == ast.none_type && val0_type.has_flag(.option) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !c.check_types(val_type, val0_type)
|
if !c.check_types(val_type, val0_type)
|
||||||
|| val0_type.has_flag(.option) != val_type.has_flag(.option)
|
|| val0_type.has_flag(.option) != val_type.has_flag(.option)
|
||||||
|| (i == 0 && val_type.is_number() && val0_type.is_number()
|
|| (i == 0 && val_type.is_number() && val0_type.is_number()
|
||||||
|
|
|
@ -129,8 +129,6 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||||
node.return_type_pos)
|
node.return_type_pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if c.table.sym(node.return_type).kind == .alias && return_sym.kind == .array_fixed {
|
|
||||||
c.error('fixed array cannot be returned by function using alias', node.return_type_pos)
|
|
||||||
}
|
}
|
||||||
// Ensure each generic type of the parameter was declared in the function's definition
|
// Ensure each generic type of the parameter was declared in the function's definition
|
||||||
if node.return_type.has_flag(.generic) {
|
if node.return_type.has_flag(.generic) {
|
||||||
|
@ -1101,7 +1099,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.
|
||||||
call_arg.pos)
|
call_arg.pos)
|
||||||
}
|
}
|
||||||
if call_arg.is_mut {
|
if call_arg.is_mut {
|
||||||
to_lock, pos := c.fail_if_immutable(call_arg.expr)
|
to_lock, pos := c.fail_if_immutable(mut call_arg.expr)
|
||||||
if !call_arg.expr.is_lvalue() {
|
if !call_arg.expr.is_lvalue() {
|
||||||
if call_arg.expr is ast.StructInit {
|
if call_arg.expr is ast.StructInit {
|
||||||
c.error('cannot pass a struct initialization as `mut`, you may want to use a variable `mut var := ${call_arg.expr}`',
|
c.error('cannot pass a struct initialization as `mut`, you may want to use a variable `mut var := ${call_arg.expr}`',
|
||||||
|
@ -1521,6 +1519,16 @@ fn (mut c Checker) cast_fixed_array_ret(typ ast.Type, sym ast.TypeSymbol) ast.Ty
|
||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cast_to_fixed_array_ret casts a ArrayFixed type created to do not return to a returning one
|
||||||
|
fn (mut c Checker) cast_to_fixed_array_ret(typ ast.Type, sym ast.TypeSymbol) ast.Type {
|
||||||
|
if sym.kind == .array_fixed && !(sym.info as ast.ArrayFixed).is_fn_ret {
|
||||||
|
info := sym.info as ast.ArrayFixed
|
||||||
|
return c.table.find_or_register_array_fixed(info.elem_type, info.size, info.size_expr,
|
||||||
|
true)
|
||||||
|
}
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
||||||
left_type := c.expr(mut node.left)
|
left_type := c.expr(mut node.left)
|
||||||
if left_type == ast.void_type {
|
if left_type == ast.void_type {
|
||||||
|
@ -1744,7 +1752,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
||||||
arg.pos)
|
arg.pos)
|
||||||
}
|
}
|
||||||
if arg.is_mut {
|
if arg.is_mut {
|
||||||
to_lock, pos := c.fail_if_immutable(arg.expr)
|
to_lock, pos := c.fail_if_immutable(mut arg.expr)
|
||||||
if !param.is_mut {
|
if !param.is_mut {
|
||||||
tok := arg.share.str()
|
tok := arg.share.str()
|
||||||
c.error('`${node.name}` parameter ${i + 1} is not `${tok}`, `${tok}` is not needed`',
|
c.error('`${node.name}` parameter ${i + 1} is not `${tok}`, `${tok}` is not needed`',
|
||||||
|
@ -1846,7 +1854,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
||||||
node.pos)
|
node.pos)
|
||||||
}
|
}
|
||||||
if method.params[0].is_mut {
|
if method.params[0].is_mut {
|
||||||
to_lock, pos := c.fail_if_immutable(node.left)
|
to_lock, pos := c.fail_if_immutable(mut node.left)
|
||||||
if !node.left.is_lvalue() {
|
if !node.left.is_lvalue() {
|
||||||
c.error('cannot pass expression as `mut`', node.left.pos())
|
c.error('cannot pass expression as `mut`', node.left.pos())
|
||||||
}
|
}
|
||||||
|
@ -1968,7 +1976,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
||||||
arg.pos)
|
arg.pos)
|
||||||
}
|
}
|
||||||
if arg.is_mut {
|
if arg.is_mut {
|
||||||
to_lock, pos := c.fail_if_immutable(arg.expr)
|
to_lock, pos := c.fail_if_immutable(mut arg.expr)
|
||||||
if !param_is_mut {
|
if !param_is_mut {
|
||||||
tok := arg.share.str()
|
tok := arg.share.str()
|
||||||
c.error('`${node.name}` parameter `${param.name}` is not `${tok}`, `${tok}` is not needed`',
|
c.error('`${node.name}` parameter `${param.name}` is not `${tok}`, `${tok}` is not needed`',
|
||||||
|
@ -2426,7 +2434,7 @@ fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.
|
||||||
c.error('`.${method_name}()` does not have any arguments', node.args[0].pos)
|
c.error('`.${method_name}()` does not have any arguments', node.args[0].pos)
|
||||||
}
|
}
|
||||||
if method_name[0] == `m` {
|
if method_name[0] == `m` {
|
||||||
c.fail_if_immutable(node.left)
|
c.fail_if_immutable(mut node.left)
|
||||||
}
|
}
|
||||||
if node.left.is_auto_deref_var() || ret_type.has_flag(.shared_f) {
|
if node.left.is_auto_deref_var() || ret_type.has_flag(.shared_f) {
|
||||||
ret_type = left_type.deref()
|
ret_type = left_type.deref()
|
||||||
|
@ -2454,7 +2462,7 @@ fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'delete' {
|
'delete' {
|
||||||
c.fail_if_immutable(node.left)
|
c.fail_if_immutable(mut node.left)
|
||||||
if node.args.len != 1 {
|
if node.args.len != 1 {
|
||||||
c.error('expected 1 argument, but got ${node.args.len}', node.pos)
|
c.error('expected 1 argument, but got ${node.args.len}', node.pos)
|
||||||
}
|
}
|
||||||
|
@ -2491,7 +2499,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
|
||||||
c.error('the `sort()` method can be called only on mutable receivers, but `${node.left}` is a call expression',
|
c.error('the `sort()` method can be called only on mutable receivers, but `${node.left}` is a call expression',
|
||||||
node.pos)
|
node.pos)
|
||||||
}
|
}
|
||||||
c.fail_if_immutable(node.left)
|
c.fail_if_immutable(mut node.left)
|
||||||
// position of `a` and `b` doesn't matter, they're the same
|
// position of `a` and `b` doesn't matter, they're the same
|
||||||
scope_register_a_b(mut node.scope, node.pos, elem_typ)
|
scope_register_a_b(mut node.scope, node.pos, elem_typ)
|
||||||
|
|
||||||
|
@ -2635,13 +2643,13 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
|
||||||
}
|
}
|
||||||
node.return_type = array_info.elem_type
|
node.return_type = array_info.elem_type
|
||||||
if method_name == 'pop' {
|
if method_name == 'pop' {
|
||||||
c.fail_if_immutable(node.left)
|
c.fail_if_immutable(mut node.left)
|
||||||
node.receiver_type = left_type.ref()
|
node.receiver_type = left_type.ref()
|
||||||
} else {
|
} else {
|
||||||
node.receiver_type = left_type
|
node.receiver_type = left_type
|
||||||
}
|
}
|
||||||
} else if method_name == 'delete' {
|
} else if method_name == 'delete' {
|
||||||
c.fail_if_immutable(node.left)
|
c.fail_if_immutable(mut node.left)
|
||||||
unwrapped_left_sym := c.table.sym(c.unwrap_generic(left_type))
|
unwrapped_left_sym := c.table.sym(c.unwrap_generic(left_type))
|
||||||
if method := c.table.find_method(unwrapped_left_sym, method_name) {
|
if method := c.table.find_method(unwrapped_left_sym, method_name) {
|
||||||
node.receiver_type = method.receiver_type
|
node.receiver_type = method.receiver_type
|
||||||
|
|
|
@ -27,8 +27,7 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
||||||
mut node_is_expr := false
|
mut node_is_expr := false
|
||||||
if node.branches.len > 0 && node.has_else {
|
if node.branches.len > 0 && node.has_else {
|
||||||
stmts := node.branches[0].stmts
|
stmts := node.branches[0].stmts
|
||||||
if stmts.len > 0 && stmts.last() is ast.ExprStmt
|
if stmts.len > 0 && stmts.last() is ast.ExprStmt && stmts.last().typ != ast.void_type {
|
||||||
&& (stmts.last() as ast.ExprStmt).typ != ast.void_type {
|
|
||||||
node_is_expr = true
|
node_is_expr = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,7 +508,7 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope) {
|
||||||
if is_variable {
|
if is_variable {
|
||||||
if (node.left is ast.Ident && node.left.is_mut)
|
if (node.left is ast.Ident && node.left.is_mut)
|
||||||
|| (node.left is ast.SelectorExpr && node.left.is_mut) {
|
|| (node.left is ast.SelectorExpr && node.left.is_mut) {
|
||||||
c.fail_if_immutable(node.left)
|
c.fail_if_immutable(mut node.left)
|
||||||
}
|
}
|
||||||
// TODO: Add check for sum types in a way that it doesn't break a lot of compiler code
|
// TODO: Add check for sum types in a way that it doesn't break a lot of compiler code
|
||||||
if mut node.left is ast.Ident
|
if mut node.left is ast.Ident
|
||||||
|
|
|
@ -505,7 +505,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||||
}
|
}
|
||||||
// `array << elm`
|
// `array << elm`
|
||||||
c.check_expr_opt_call(node.right, right_type)
|
c.check_expr_opt_call(node.right, right_type)
|
||||||
node.auto_locked, _ = c.fail_if_immutable(node.left)
|
node.auto_locked, _ = c.fail_if_immutable(mut node.left)
|
||||||
left_value_type := c.table.value_type(c.unwrap_generic(left_type))
|
left_value_type := c.table.value_type(c.unwrap_generic(left_type))
|
||||||
left_value_sym := c.table.sym(c.unwrap_generic(left_value_type))
|
left_value_sym := c.table.sym(c.unwrap_generic(left_value_type))
|
||||||
if left_value_sym.kind == .interface_ {
|
if left_value_sym.kind == .interface_ {
|
||||||
|
@ -661,7 +661,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||||
}
|
}
|
||||||
if chan_info.is_mut {
|
if chan_info.is_mut {
|
||||||
// TODO: The error message of the following could be more specific...
|
// TODO: The error message of the following could be more specific...
|
||||||
c.fail_if_immutable(node.right)
|
c.fail_if_immutable(mut node.right)
|
||||||
}
|
}
|
||||||
if elem_type.is_ptr() && !right_type.is_ptr() {
|
if elem_type.is_ptr() && !right_type.is_ptr() {
|
||||||
c.error('cannot push non-reference `${right_sym.name}` on `${left_sym.name}`',
|
c.error('cannot push non-reference `${right_sym.name}` on `${left_sym.name}`',
|
||||||
|
@ -779,9 +779,8 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
if (node.left is ast.InfixExpr &&
|
if (node.left is ast.InfixExpr && node.left.op == .inc) ||
|
||||||
(node.left as ast.InfixExpr).op == .inc) ||
|
(node.right is ast.InfixExpr && node.right.op == .inc) {
|
||||||
(node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) {
|
|
||||||
c.warn('`++` and `--` are statements, not expressions', node.pos)
|
c.warn('`++` and `--` are statements, not expressions', node.pos)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
@ -857,6 +856,11 @@ fn (mut c Checker) autocast_in_if_conds(mut right ast.Expr, from_expr ast.Expr,
|
||||||
ast.ParExpr {
|
ast.ParExpr {
|
||||||
c.autocast_in_if_conds(mut right.expr, from_expr, from_type, to_type)
|
c.autocast_in_if_conds(mut right.expr, from_expr, from_type, to_type)
|
||||||
}
|
}
|
||||||
|
ast.AsCast {
|
||||||
|
if right.typ != to_type {
|
||||||
|
c.autocast_in_if_conds(mut right.expr, from_expr, from_type, to_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
ast.PrefixExpr {
|
ast.PrefixExpr {
|
||||||
c.autocast_in_if_conds(mut right.right, from_expr, from_type, to_type)
|
c.autocast_in_if_conds(mut right.right, from_expr, from_type, to_type)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
|
||||||
node.cond_type = ast.mktyp(cond_type)
|
node.cond_type = ast.mktyp(cond_type)
|
||||||
if (node.cond is ast.Ident && node.cond.is_mut)
|
if (node.cond is ast.Ident && node.cond.is_mut)
|
||||||
|| (node.cond is ast.SelectorExpr && node.cond.is_mut) {
|
|| (node.cond is ast.SelectorExpr && node.cond.is_mut) {
|
||||||
c.fail_if_immutable(node.cond)
|
c.fail_if_immutable(mut node.cond)
|
||||||
}
|
}
|
||||||
c.ensure_type_exists(node.cond_type, node.pos) or { return ast.void_type }
|
c.ensure_type_exists(node.cond_type, node.pos) or { return ast.void_type }
|
||||||
c.check_expr_opt_call(node.cond, cond_type)
|
c.check_expr_opt_call(node.cond, cond_type)
|
||||||
|
|
|
@ -37,7 +37,7 @@ fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) ast.Type {
|
||||||
c.error('invalid operation: ${node.op.str()} (non-numeric type `${typ_str}`)',
|
c.error('invalid operation: ${node.op.str()} (non-numeric type `${typ_str}`)',
|
||||||
node.pos)
|
node.pos)
|
||||||
} else {
|
} else {
|
||||||
node.auto_locked, _ = c.fail_if_immutable(node.expr)
|
node.auto_locked, _ = c.fail_if_immutable(mut node.expr)
|
||||||
}
|
}
|
||||||
node.typ = typ
|
node.typ = typ
|
||||||
return typ
|
return typ
|
||||||
|
|
|
@ -179,6 +179,11 @@ fn (mut c Checker) return_stmt(mut node ast.Return) {
|
||||||
c.error('cannot use `${c.table.type_to_str(got_type)}` as ${c.error_type_name(exp_type)} in return argument',
|
c.error('cannot use `${c.table.type_to_str(got_type)}` as ${c.error_type_name(exp_type)} in return argument',
|
||||||
pos)
|
pos)
|
||||||
}
|
}
|
||||||
|
if exprv is ast.ComptimeCall && exprv.method_name == 'tmpl'
|
||||||
|
&& c.table.final_sym(exp_type).kind != .string {
|
||||||
|
c.error('cannot use `string` as type `${c.table.type_to_str(exp_type)}` in return argument',
|
||||||
|
exprv.pos)
|
||||||
|
}
|
||||||
if node.exprs[expr_idxs[i]] !is ast.ComptimeCall {
|
if node.exprs[expr_idxs[i]] !is ast.ComptimeCall {
|
||||||
got_type_sym := c.table.sym(got_type)
|
got_type_sym := c.table.sym(got_type)
|
||||||
exp_type_sym := c.table.sym(exp_type)
|
exp_type_sym := c.table.sym(exp_type)
|
||||||
|
@ -203,8 +208,11 @@ fn (mut c Checker) return_stmt(mut node ast.Return) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if exp_type_sym.kind == .array_fixed && got_type_sym.kind == .array_fixed {
|
exp_final_sym := c.table.final_sym(exp_type)
|
||||||
if (exp_type_sym.info as ast.ArrayFixed).size == (got_type_sym.info as ast.ArrayFixed).size && (exp_type_sym.info as ast.ArrayFixed).elem_type == (got_type_sym.info as ast.ArrayFixed).elem_type {
|
got_final_sym := c.table.final_sym(got_type)
|
||||||
|
if exp_final_sym.kind == .array_fixed && got_final_sym.kind == .array_fixed {
|
||||||
|
got_arr_sym := c.table.sym(c.cast_to_fixed_array_ret(got_type, got_final_sym))
|
||||||
|
if (exp_final_sym.info as ast.ArrayFixed).is_compatible(got_arr_sym.info as ast.ArrayFixed) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
vlib/v/checker/tests/auto_deref_assign_err.out
Normal file
6
vlib/v/checker/tests/auto_deref_assign_err.out
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
vlib/v/checker/tests/auto_deref_assign_err.vv:10:6: error: cannot assign to `o`: expected `&int`, not `string`
|
||||||
|
8 | fn sub( mut o &int ) {
|
||||||
|
9 | println( 'in function got: ' + o.str() )
|
||||||
|
10 | o = "mutate int as string??"
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
11 | }
|
11
vlib/v/checker/tests/auto_deref_assign_err.vv
Normal file
11
vlib/v/checker/tests/auto_deref_assign_err.vv
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fn main() {
|
||||||
|
mut a := 42
|
||||||
|
println( 'before sub: ' + a.str() )
|
||||||
|
sub( mut a )
|
||||||
|
println( 'after sub: ' + a.str() )
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub( mut o &int ) {
|
||||||
|
println( 'in function got: ' + o.str() )
|
||||||
|
o = "mutate int as string??"
|
||||||
|
}
|
7
vlib/v/checker/tests/closure_copy_immutable_var_err.out
Normal file
7
vlib/v/checker/tests/closure_copy_immutable_var_err.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/closure_copy_immutable_var_err.vv:5:3: error: the closure copy of `name` is immutable, declare it with `mut` to make it mutable
|
||||||
|
3 |
|
||||||
|
4 | fn [name] () {
|
||||||
|
5 | name = 'Ivan'
|
||||||
|
| ~~~~
|
||||||
|
6 | println(name)
|
||||||
|
7 | }()
|
8
vlib/v/checker/tests/closure_copy_immutable_var_err.vv
Normal file
8
vlib/v/checker/tests/closure_copy_immutable_var_err.vv
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
fn main() {
|
||||||
|
mut name := 'John'
|
||||||
|
|
||||||
|
fn [name] () {
|
||||||
|
name = 'Ivan'
|
||||||
|
println(name)
|
||||||
|
}()
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/closure_immutable.vv:4:3: error: `a` is immutable, declare it with `mut` to make it mutable
|
vlib/v/checker/tests/closure_immutable.vv:4:3: error: the closure copy of `a` is immutable, declare it with `mut` to make it mutable
|
||||||
2 | a := 1
|
2 | a := 1
|
||||||
3 | f1 := fn [a] () {
|
3 | f1 := fn [a] () {
|
||||||
4 | a++
|
4 | a++
|
||||||
|
@ -12,7 +12,7 @@ vlib/v/checker/tests/closure_immutable.vv:7:16: error: original `a` is immutable
|
||||||
| ^
|
| ^
|
||||||
8 | a++
|
8 | a++
|
||||||
9 | println(a)
|
9 | println(a)
|
||||||
vlib/v/checker/tests/closure_immutable.vv:13:3: error: `b` is immutable, declare it with `mut` to make it mutable
|
vlib/v/checker/tests/closure_immutable.vv:13:3: error: the closure copy of `b` is immutable, declare it with `mut` to make it mutable
|
||||||
11 | mut b := 2
|
11 | mut b := 2
|
||||||
12 | f3 := fn [b] () {
|
12 | f3 := fn [b] () {
|
||||||
13 | b++
|
13 | b++
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/const_field_name_snake_case.vv:2:2: warning: const names cannot contain uppercase letters, use snake_case instead
|
vlib/v/checker/tests/const_field_name_snake_case.vv:2:2: error: const names cannot contain uppercase letters, use snake_case instead
|
||||||
1 | const (
|
1 | const (
|
||||||
2 | Red = 1
|
2 | Red = 1
|
||||||
| ~~~
|
| ~~~
|
||||||
|
|
34
vlib/v/checker/tests/map_with_none_err.out
Normal file
34
vlib/v/checker/tests/map_with_none_err.out
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
vlib/v/checker/tests/map_with_none_err.vv:2:6: warning: unused variable: `a`
|
||||||
|
1 | fn main() {
|
||||||
|
2 | mut a := {
|
||||||
|
| ^
|
||||||
|
3 | 'bar': none
|
||||||
|
4 | }
|
||||||
|
vlib/v/checker/tests/map_with_none_err.vv:6:6: warning: unused variable: `b`
|
||||||
|
4 | }
|
||||||
|
5 |
|
||||||
|
6 | mut b := {
|
||||||
|
| ^
|
||||||
|
7 | 'foo': 1,
|
||||||
|
8 | 'bar': none
|
||||||
|
vlib/v/checker/tests/map_with_none_err.vv:11:6: warning: unused variable: `c`
|
||||||
|
9 | }
|
||||||
|
10 |
|
||||||
|
11 | mut c := {
|
||||||
|
| ^
|
||||||
|
12 | 'foo': ?int(none),
|
||||||
|
13 | 'bar': none
|
||||||
|
vlib/v/checker/tests/map_with_none_err.vv:3:10: error: map value cannot be only `none`
|
||||||
|
1 | fn main() {
|
||||||
|
2 | mut a := {
|
||||||
|
3 | 'bar': none
|
||||||
|
| ~~~~
|
||||||
|
4 | }
|
||||||
|
5 |
|
||||||
|
vlib/v/checker/tests/map_with_none_err.vv:8:10: error: invalid map value: expected `int`, not `none`
|
||||||
|
6 | mut b := {
|
||||||
|
7 | 'foo': 1,
|
||||||
|
8 | 'bar': none
|
||||||
|
| ~~~~
|
||||||
|
9 | }
|
||||||
|
10 |
|
15
vlib/v/checker/tests/map_with_none_err.vv
Normal file
15
vlib/v/checker/tests/map_with_none_err.vv
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
fn main() {
|
||||||
|
mut a := {
|
||||||
|
'bar': none
|
||||||
|
}
|
||||||
|
|
||||||
|
mut b := {
|
||||||
|
'foo': 1,
|
||||||
|
'bar': none
|
||||||
|
}
|
||||||
|
|
||||||
|
mut c := {
|
||||||
|
'foo': ?int(none),
|
||||||
|
'bar': none
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
vlib/v/checker/tests/return_fixed_array.vv:11:25: error: fixed array cannot be returned by function using alias
|
|
||||||
9 | }
|
|
||||||
10 |
|
|
||||||
11 | fn return_fixed_array() Abc {
|
|
||||||
| ~~~
|
|
||||||
12 | return [1, 2, 3]!
|
|
||||||
13 | }
|
|
|
@ -1,13 +0,0 @@
|
||||||
type Abc = [3]int
|
|
||||||
|
|
||||||
fn return_fixed_array() [3]int {
|
|
||||||
return [1, 2, 3]!
|
|
||||||
}
|
|
||||||
|
|
||||||
fn return_fixed_array_in_multi_return() ([3]int, [3]int) {
|
|
||||||
return [1, 2, 3]!, [4, 5, 6]!
|
|
||||||
}
|
|
||||||
|
|
||||||
fn return_fixed_array() Abc {
|
|
||||||
return [1, 2, 3]!
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
vlib/v/checker/tests/struct_field_with_any_type_err.vv:2:6: error: cannot use `any` type here, `any` will be implemented in V 0.4
|
vlib/v/checker/tests/struct_field_with_any_type_err.vv:2:6: error: cannot use `any` type here
|
||||||
1 | struct My_type {
|
1 | struct My_type {
|
||||||
2 | fld any
|
2 | fld any
|
||||||
| ~~~
|
| ~~~
|
||||||
|
|
6
vlib/v/checker/tests/template_type_mismatch_err.out
Normal file
6
vlib/v/checker/tests/template_type_mismatch_err.out
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
vlib/v/checker/tests/template_type_mismatch_err.vv:6:9: error: cannot use `string` as type `int` in return argument
|
||||||
|
4 |
|
||||||
|
5 | fn return_item() int {
|
||||||
|
6 | return $tmpl('./templates/template.md')
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
7 | }
|
7
vlib/v/checker/tests/template_type_mismatch_err.vv
Normal file
7
vlib/v/checker/tests/template_type_mismatch_err.vv
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
fn main() {
|
||||||
|
println(return_item())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_item() int {
|
||||||
|
return $tmpl('./templates/template.md')
|
||||||
|
}
|
1
vlib/v/checker/tests/templates/template.md
Normal file
1
vlib/v/checker/tests/templates/template.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
7
vlib/v/checker/tests/voidptr_cast_to_struct_err.out
Normal file
7
vlib/v/checker/tests/voidptr_cast_to_struct_err.out
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/voidptr_cast_to_struct_err.vv:4:7: error: cannot cast `voidptr` to struct
|
||||||
|
2 |
|
||||||
|
3 | fn main() {
|
||||||
|
4 | a := Foo(unsafe { nil })
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~
|
||||||
|
5 | println(a)
|
||||||
|
6 | }
|
6
vlib/v/checker/tests/voidptr_cast_to_struct_err.vv
Normal file
6
vlib/v/checker/tests/voidptr_cast_to_struct_err.vv
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
struct Foo {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
a := Foo(unsafe { nil })
|
||||||
|
println(a)
|
||||||
|
}
|
|
@ -127,9 +127,11 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
is_decl := node.op == .decl_assign
|
is_decl := node.op == .decl_assign
|
||||||
g.assign_op = node.op
|
g.assign_op = node.op
|
||||||
g.inside_assign = true
|
g.inside_assign = true
|
||||||
|
g.assign_ct_type = 0
|
||||||
defer {
|
defer {
|
||||||
g.assign_op = .unknown
|
g.assign_op = .unknown
|
||||||
g.inside_assign = false
|
g.inside_assign = false
|
||||||
|
g.assign_ct_type = 0
|
||||||
}
|
}
|
||||||
op := if is_decl { token.Kind.assign } else { node.op }
|
op := if is_decl { token.Kind.assign } else { node.op }
|
||||||
right_expr := node.right[0]
|
right_expr := node.right[0]
|
||||||
|
@ -241,6 +243,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
var_type = val_type.clear_flag(.option)
|
var_type = val_type.clear_flag(.option)
|
||||||
}
|
}
|
||||||
left.obj.typ = var_type
|
left.obj.typ = var_type
|
||||||
|
g.assign_ct_type = var_type
|
||||||
}
|
}
|
||||||
} else if val is ast.ComptimeSelector {
|
} else if val is ast.ComptimeSelector {
|
||||||
key_str := g.get_comptime_selector_key_type(val)
|
key_str := g.get_comptime_selector_key_type(val)
|
||||||
|
@ -252,11 +255,13 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
} else {
|
} else {
|
||||||
val_type = g.comptime_var_type_map[key_str] or { var_type }
|
val_type = g.comptime_var_type_map[key_str] or { var_type }
|
||||||
}
|
}
|
||||||
|
g.assign_ct_type = var_type
|
||||||
}
|
}
|
||||||
} else if val is ast.ComptimeCall {
|
} else if val is ast.ComptimeCall {
|
||||||
key_str := '${val.method_name}.return_type'
|
key_str := '${val.method_name}.return_type'
|
||||||
var_type = g.comptime_var_type_map[key_str] or { var_type }
|
var_type = g.comptime_var_type_map[key_str] or { var_type }
|
||||||
left.obj.typ = var_type
|
left.obj.typ = var_type
|
||||||
|
g.assign_ct_type = var_type
|
||||||
} else if is_decl && val is ast.Ident && val.info is ast.IdentVar {
|
} else if is_decl && val is ast.Ident && val.info is ast.IdentVar {
|
||||||
val_info := (val as ast.Ident).info
|
val_info := (val as ast.Ident).info
|
||||||
gen_or = val.or_expr.kind != .absent
|
gen_or = val.or_expr.kind != .absent
|
||||||
|
@ -271,6 +276,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
val_type = var_type
|
val_type = var_type
|
||||||
left.obj.typ = var_type
|
left.obj.typ = var_type
|
||||||
}
|
}
|
||||||
|
g.assign_ct_type = var_type
|
||||||
} else if val is ast.IndexExpr {
|
} else if val is ast.IndexExpr {
|
||||||
if val.left is ast.Ident && g.is_generic_param_var(val.left) {
|
if val.left is ast.Ident && g.is_generic_param_var(val.left) {
|
||||||
ctyp := g.unwrap_generic(g.get_gn_var_type(val.left as ast.Ident))
|
ctyp := g.unwrap_generic(g.get_gn_var_type(val.left as ast.Ident))
|
||||||
|
@ -278,6 +284,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
var_type = ctyp
|
var_type = ctyp
|
||||||
val_type = var_type
|
val_type = var_type
|
||||||
left.obj.typ = var_type
|
left.obj.typ = var_type
|
||||||
|
g.assign_ct_type = var_type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,11 +301,13 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
val_type = g.comptime_var_type_map[key_str_right] or { var_type }
|
val_type = g.comptime_var_type_map[key_str_right] or { var_type }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
g.assign_ct_type = var_type
|
||||||
} else if mut left is ast.IndexExpr && val is ast.ComptimeSelector {
|
} else if mut left is ast.IndexExpr && val is ast.ComptimeSelector {
|
||||||
key_str := g.get_comptime_selector_key_type(val)
|
key_str := g.get_comptime_selector_key_type(val)
|
||||||
if key_str != '' {
|
if key_str != '' {
|
||||||
val_type = g.comptime_var_type_map[key_str] or { var_type }
|
val_type = g.comptime_var_type_map[key_str] or { var_type }
|
||||||
}
|
}
|
||||||
|
g.assign_ct_type = val_type
|
||||||
}
|
}
|
||||||
mut styp := g.typ(var_type)
|
mut styp := g.typ(var_type)
|
||||||
mut is_fixed_array_init := false
|
mut is_fixed_array_init := false
|
||||||
|
@ -569,9 +578,22 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !is_used_var_styp {
|
if !is_used_var_styp {
|
||||||
if !val_type.has_flag(.option) && left_sym.info is ast.ArrayFixed
|
if !val_type.has_flag(.option) && left_sym.is_array_fixed() {
|
||||||
&& left_sym.info.is_fn_ret {
|
if left_sym.kind == .alias {
|
||||||
g.write('${styp[3..]} ')
|
parent_sym := g.table.final_sym((left_sym.info as ast.Alias).parent_type)
|
||||||
|
styp = g.typ((left_sym.info as ast.Alias).parent_type)
|
||||||
|
if !parent_sym.is_array_fixed_ret() {
|
||||||
|
g.write('${styp} ')
|
||||||
|
} else {
|
||||||
|
g.write('${styp[3..]} ')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !left_sym.is_array_fixed_ret() {
|
||||||
|
g.write('${styp} ')
|
||||||
|
} else {
|
||||||
|
g.write('${styp[3..]} ')
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
g.write('${styp} ')
|
g.write('${styp} ')
|
||||||
}
|
}
|
||||||
|
@ -692,8 +714,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
||||||
// var = &auto_heap_var
|
// var = &auto_heap_var
|
||||||
old_is_auto_heap := g.is_option_auto_heap
|
old_is_auto_heap := g.is_option_auto_heap
|
||||||
g.is_option_auto_heap = val_type.has_flag(.option) && val is ast.PrefixExpr
|
g.is_option_auto_heap = val_type.has_flag(.option) && val is ast.PrefixExpr
|
||||||
&& val.right is ast.Ident
|
&& val.right is ast.Ident && (val.right as ast.Ident).is_auto_heap()
|
||||||
&& ((val as ast.PrefixExpr).right as ast.Ident).is_auto_heap()
|
|
||||||
defer {
|
defer {
|
||||||
g.is_option_auto_heap = old_is_auto_heap
|
g.is_option_auto_heap = old_is_auto_heap
|
||||||
}
|
}
|
||||||
|
@ -889,7 +910,7 @@ fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.IndexExpr {
|
ast.IndexExpr {
|
||||||
sym := g.table.sym(left.left_type)
|
sym := g.table.sym(g.table.unaliased_type(left.left_type))
|
||||||
if sym.kind == .array {
|
if sym.kind == .array {
|
||||||
info := sym.info as ast.Array
|
info := sym.info as ast.Array
|
||||||
elem_typ := g.table.sym(info.elem_type)
|
elem_typ := g.table.sym(info.elem_type)
|
||||||
|
|
|
@ -783,24 +783,37 @@ fn (mut g Gen) gen_str_for_map(info ast.Map, styp string, str_fn_name string) {
|
||||||
if val_sym.kind == .function {
|
if val_sym.kind == .function {
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}());')
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}());')
|
||||||
} else if val_sym.kind == .string {
|
} else if val_sym.kind == .string {
|
||||||
tmp_str := str_intp_sq('*(${val_styp}*)DenseArray_value(&m.key_values, i)')
|
if val_typ.has_flag(.option) {
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
|
func := g.get_str_fn(val_typ)
|
||||||
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${func}(*(${val_styp}*)DenseArray_value(&m.key_values, i)));')
|
||||||
|
} else {
|
||||||
|
tmp_str := str_intp_sq('*(${val_styp}*)DenseArray_value(&m.key_values, i)')
|
||||||
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
|
||||||
|
}
|
||||||
} else if should_use_indent_func(val_sym.kind) && fn_str.name != 'str' {
|
} else if should_use_indent_func(val_sym.kind) && fn_str.name != 'str' {
|
||||||
ptr_str := '*'.repeat(val_typ.nr_muls())
|
ptr_str := '*'.repeat(val_typ.nr_muls())
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(*${ptr_str}(${val_styp}*)DenseArray_value(&m.key_values, i), indent_count));')
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(*${ptr_str}(${val_styp}*)DenseArray_value(&m.key_values, i), indent_count));')
|
||||||
} else if val_sym.kind in [.f32, .f64] {
|
} else if val_sym.kind in [.f32, .f64] {
|
||||||
tmp_val := '*(${val_styp}*)DenseArray_value(&m.key_values, i)'
|
tmp_val := '*(${val_styp}*)DenseArray_value(&m.key_values, i)'
|
||||||
if val_sym.kind == .f32 {
|
if val_typ.has_flag(.option) {
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32(tmp_val)});')
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${g.get_str_fn(val_typ)}(*(${val_styp}*)DenseArray_value(&m.key_values, i)));')
|
||||||
} else {
|
} else {
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64(tmp_val)});')
|
if val_sym.kind == .f32 {
|
||||||
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32(tmp_val)});')
|
||||||
|
} else {
|
||||||
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64(tmp_val)});')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if val_sym.kind == .rune {
|
} else if val_sym.kind == .rune {
|
||||||
tmp_str := str_intp_rune('${elem_str_fn_name}(*(${val_styp}*)DenseArray_value(&m.key_values, i))')
|
tmp_str := str_intp_rune('${elem_str_fn_name}(*(${val_styp}*)DenseArray_value(&m.key_values, i))')
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${tmp_str});')
|
||||||
} else {
|
} else {
|
||||||
ptr_str := '*'.repeat(if receiver_is_ptr { val_typ.nr_muls() - 1 } else { val_typ.nr_muls() })
|
ptr_str := '*'.repeat(if receiver_is_ptr { val_typ.nr_muls() - 1 } else { val_typ.nr_muls() })
|
||||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(*${ptr_str}(${val_styp}*)DenseArray_value(&m.key_values, i)));')
|
if val_typ.has_flag(.option) {
|
||||||
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${g.get_str_fn(val_typ)}(*${ptr_str}(${val_styp}*)DenseArray_value(&m.key_values, i)));')
|
||||||
|
} else {
|
||||||
|
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(*${ptr_str}(${val_styp}*)DenseArray_value(&m.key_values, i)));')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g.auto_str_funcs.writeln('\t\tis_first = false;')
|
g.auto_str_funcs.writeln('\t\tis_first = false;')
|
||||||
g.auto_str_funcs.writeln('\t}')
|
g.auto_str_funcs.writeln('\t}')
|
||||||
|
|
|
@ -150,11 +150,12 @@ mut:
|
||||||
loop_depth int
|
loop_depth int
|
||||||
ternary_names map[string]string
|
ternary_names map[string]string
|
||||||
ternary_level_names map[string][]string
|
ternary_level_names map[string][]string
|
||||||
arraymap_set_pos int // map or array set value position
|
arraymap_set_pos int // map or array set value position
|
||||||
stmt_path_pos []int // positions of each statement start, for inserting C statements before the current statement
|
stmt_path_pos []int // positions of each statement start, for inserting C statements before the current statement
|
||||||
skip_stmt_pos bool // for handling if expressions + autofree (since both prepend C statements)
|
skip_stmt_pos bool // for handling if expressions + autofree (since both prepend C statements)
|
||||||
left_is_opt bool // left hand side on assignment is an option
|
left_is_opt bool // left hand side on assignment is an option
|
||||||
right_is_opt bool // right hand side on assignment is an option
|
right_is_opt bool // right hand side on assignment is an option
|
||||||
|
assign_ct_type ast.Type // left hand side resolved comptime type
|
||||||
indent int
|
indent int
|
||||||
empty_line bool
|
empty_line bool
|
||||||
assign_op token.Kind // *=, =, etc (for array_set)
|
assign_op token.Kind // *=, =, etc (for array_set)
|
||||||
|
@ -1064,9 +1065,12 @@ fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string
|
||||||
// all unified in one place so that it doesnt break
|
// all unified in one place so that it doesnt break
|
||||||
// if one location changes
|
// if one location changes
|
||||||
fn (mut g Gen) option_type_name(t ast.Type) (string, string) {
|
fn (mut g Gen) option_type_name(t ast.Type) (string, string) {
|
||||||
base := g.base_type(t)
|
mut base := g.base_type(t)
|
||||||
mut styp := ''
|
mut styp := ''
|
||||||
sym := g.table.sym(t)
|
sym := g.table.sym(t)
|
||||||
|
if sym.info is ast.FnType {
|
||||||
|
base = 'anon_fn_${g.table.fn_type_signature(sym.info.func)}'
|
||||||
|
}
|
||||||
if sym.language == .c && sym.kind == .struct_ {
|
if sym.language == .c && sym.kind == .struct_ {
|
||||||
styp = '${c.option_name}_${base.replace(' ', '_')}'
|
styp = '${c.option_name}_${base.replace(' ', '_')}'
|
||||||
} else {
|
} else {
|
||||||
|
@ -1086,6 +1090,9 @@ fn (mut g Gen) result_type_name(t ast.Type) (string, string) {
|
||||||
}
|
}
|
||||||
mut styp := ''
|
mut styp := ''
|
||||||
sym := g.table.sym(t)
|
sym := g.table.sym(t)
|
||||||
|
if sym.info is ast.FnType {
|
||||||
|
base = 'anon_fn_${g.table.fn_type_signature(sym.info.func)}'
|
||||||
|
}
|
||||||
if sym.language == .c && sym.kind == .struct_ {
|
if sym.language == .c && sym.kind == .struct_ {
|
||||||
styp = '${c.result_name}_${base.replace(' ', '_')}'
|
styp = '${c.result_name}_${base.replace(' ', '_')}'
|
||||||
} else {
|
} else {
|
||||||
|
@ -1898,7 +1905,7 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T
|
||||||
}
|
}
|
||||||
if ret_typ.has_flag(.option) {
|
if ret_typ.has_flag(.option) {
|
||||||
if expr_typ.has_flag(.option) && expr in [ast.StructInit, ast.ArrayInit, ast.MapInit] {
|
if expr_typ.has_flag(.option) && expr in [ast.StructInit, ast.ArrayInit, ast.MapInit] {
|
||||||
if expr is ast.StructInit && (expr as ast.StructInit).init_fields.len > 0 {
|
if expr is ast.StructInit && expr.init_fields.len > 0 {
|
||||||
g.write('_option_ok(&(${styp}[]) { ')
|
g.write('_option_ok(&(${styp}[]) { ')
|
||||||
} else {
|
} else {
|
||||||
g.write('_option_none(&(${styp}[]) { ')
|
g.write('_option_none(&(${styp}[]) { ')
|
||||||
|
@ -4018,7 +4025,7 @@ fn (mut g Gen) map_init(node ast.MapInit) {
|
||||||
}
|
}
|
||||||
if value_sym.kind == .sum_type {
|
if value_sym.kind == .sum_type {
|
||||||
g.expr_with_cast(expr, node.val_types[i], unwrap_val_typ)
|
g.expr_with_cast(expr, node.val_types[i], unwrap_val_typ)
|
||||||
} else if node.val_types[i].has_flag(.option) {
|
} else if node.val_types[i].has_flag(.option) || node.val_types[i] == ast.none_type {
|
||||||
g.expr_with_opt(expr, node.val_types[i], unwrap_val_typ)
|
g.expr_with_opt(expr, node.val_types[i], unwrap_val_typ)
|
||||||
} else {
|
} else {
|
||||||
g.expr(expr)
|
g.expr(expr)
|
||||||
|
@ -4194,13 +4201,13 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) {
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut g Gen) is_generic_param_var(node ast.Expr) bool {
|
pub fn (mut g Gen) is_generic_param_var(node ast.Expr) bool {
|
||||||
return node is ast.Ident && node.info is ast.IdentVar && node.obj is ast.Var
|
return node is ast.Ident && node.info is ast.IdentVar && node.obj is ast.Var
|
||||||
&& ((node as ast.Ident).obj as ast.Var).ct_type_var == .generic_param
|
&& (node.obj as ast.Var).ct_type_var == .generic_param
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut g Gen) is_comptime_var(node ast.Expr) bool {
|
pub fn (mut g Gen) is_comptime_var(node ast.Expr) bool {
|
||||||
return node is ast.Ident && node.info is ast.IdentVar && node.obj is ast.Var
|
return node is ast.Ident && node.info is ast.IdentVar && node.obj is ast.Var
|
||||||
&& ((node as ast.Ident).obj as ast.Var).ct_type_var != .no_comptime
|
&& (node.obj as ast.Var).ct_type_var != .no_comptime
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) ident(node ast.Ident) {
|
fn (mut g Gen) ident(node ast.Ident) {
|
||||||
|
@ -4753,7 +4760,7 @@ fn (mut g Gen) return_stmt(node ast.Return) {
|
||||||
fn_return_is_multi := sym.kind == .multi_return
|
fn_return_is_multi := sym.kind == .multi_return
|
||||||
fn_return_is_option := fn_ret_type.has_flag(.option)
|
fn_return_is_option := fn_ret_type.has_flag(.option)
|
||||||
fn_return_is_result := fn_ret_type.has_flag(.result)
|
fn_return_is_result := fn_ret_type.has_flag(.result)
|
||||||
fn_return_is_fixed_array := sym.kind == .array_fixed && !fn_ret_type.has_flag(.option)
|
fn_return_is_fixed_array := sym.is_array_fixed() && !fn_ret_type.has_flag(.option)
|
||||||
|
|
||||||
mut has_semicolon := false
|
mut has_semicolon := false
|
||||||
if node.exprs.len == 0 {
|
if node.exprs.len == 0 {
|
||||||
|
@ -4775,6 +4782,21 @@ fn (mut g Gen) return_stmt(node ast.Return) {
|
||||||
mut ret_typ := g.typ(g.unwrap_generic(fn_ret_type))
|
mut ret_typ := g.typ(g.unwrap_generic(fn_ret_type))
|
||||||
if fn_ret_type.has_flag(.generic) && fn_return_is_fixed_array {
|
if fn_ret_type.has_flag(.generic) && fn_return_is_fixed_array {
|
||||||
ret_typ = '_v_${ret_typ}'
|
ret_typ = '_v_${ret_typ}'
|
||||||
|
} else if sym.kind == .alias && fn_return_is_fixed_array {
|
||||||
|
ret_typ = '_v_' + g.typ((sym.info as ast.Alias).parent_type)
|
||||||
|
}
|
||||||
|
if node.exprs.len == 1 {
|
||||||
|
// `return fn_call_opt()`
|
||||||
|
if (fn_return_is_option || fn_return_is_result) && node.exprs[0] is ast.CallExpr
|
||||||
|
&& node.exprs[0].return_type == g.fn_decl.return_type
|
||||||
|
&& node.exprs[0].or_block.kind == .absent {
|
||||||
|
g.write('${ret_typ} ${tmpvar} = ')
|
||||||
|
g.expr(node.exprs[0])
|
||||||
|
g.writeln(';')
|
||||||
|
g.write_defer_stmts_when_needed()
|
||||||
|
g.writeln('return ${tmpvar};')
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mut use_tmp_var := g.defer_stmts.len > 0 || g.defer_profile_code.len > 0
|
mut use_tmp_var := g.defer_stmts.len > 0 || g.defer_profile_code.len > 0
|
||||||
|| g.cur_lock.lockeds.len > 0
|
|| g.cur_lock.lockeds.len > 0
|
||||||
|
@ -6115,11 +6137,8 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty
|
||||||
}
|
}
|
||||||
if or_block.kind == .block {
|
if or_block.kind == .block {
|
||||||
g.or_expr_return_type = return_type.clear_flags(.option, .result)
|
g.or_expr_return_type = return_type.clear_flags(.option, .result)
|
||||||
if g.inside_or_block {
|
g.writeln('\tIError err = ${cvar_name}.err;')
|
||||||
g.writeln('\terr = ${cvar_name}.err;')
|
|
||||||
} else {
|
|
||||||
g.writeln('\tIError err = ${cvar_name}.err;')
|
|
||||||
}
|
|
||||||
g.inside_or_block = true
|
g.inside_or_block = true
|
||||||
defer {
|
defer {
|
||||||
g.inside_or_block = false
|
g.inside_or_block = false
|
||||||
|
|
|
@ -100,6 +100,7 @@ fn (mut g Gen) dump_expr_definitions() {
|
||||||
}
|
}
|
||||||
str_dumparg_type += g.cc_type(dump_type, true) + ptr_asterisk
|
str_dumparg_type += g.cc_type(dump_type, true) + ptr_asterisk
|
||||||
}
|
}
|
||||||
|
mut is_fixed_arr_ret := false
|
||||||
if dump_sym.kind == .function {
|
if dump_sym.kind == .function {
|
||||||
fninfo := dump_sym.info as ast.FnType
|
fninfo := dump_sym.info as ast.FnType
|
||||||
str_dumparg_type = 'DumpFNType_${name}'
|
str_dumparg_type = 'DumpFNType_${name}'
|
||||||
|
@ -109,14 +110,30 @@ fn (mut g Gen) dump_expr_definitions() {
|
||||||
g.go_back(str_tdef.len)
|
g.go_back(str_tdef.len)
|
||||||
dump_typedefs['typedef ${str_tdef};'] = true
|
dump_typedefs['typedef ${str_tdef};'] = true
|
||||||
str_dumparg_ret_type = str_dumparg_type
|
str_dumparg_ret_type = str_dumparg_type
|
||||||
} else if !typ.has_flag(.option) && dump_sym.kind == .array_fixed {
|
} else if !typ.has_flag(.option) && dump_sym.is_array_fixed() {
|
||||||
if (dump_sym.info as ast.ArrayFixed).is_fn_ret {
|
match dump_sym.kind {
|
||||||
str_dumparg_ret_type = str_dumparg_type
|
.array_fixed {
|
||||||
str_dumparg_type = str_dumparg_type.trim_string_left('_v_')
|
if (dump_sym.info as ast.ArrayFixed).is_fn_ret {
|
||||||
} else {
|
str_dumparg_ret_type = str_dumparg_type
|
||||||
// fixed array returned from function
|
str_dumparg_type = str_dumparg_type.trim_string_left('_v_')
|
||||||
str_dumparg_ret_type = '_v_' + str_dumparg_type
|
} else {
|
||||||
|
// fixed array returned from function
|
||||||
|
str_dumparg_ret_type = '_v_' + str_dumparg_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alias {
|
||||||
|
parent_sym := g.table.sym((dump_sym.info as ast.Alias).parent_type)
|
||||||
|
if parent_sym.kind == .array_fixed {
|
||||||
|
str_dumparg_ret_type =
|
||||||
|
if (parent_sym.info as ast.ArrayFixed).is_fn_ret { '' } else { '_v_' } +
|
||||||
|
g.cc_type((dump_sym.info as ast.Alias).parent_type, true)
|
||||||
|
str_dumparg_type = str_dumparg_ret_type.trim_string_left('_v_')
|
||||||
|
is_fixed_arr_ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {}
|
||||||
}
|
}
|
||||||
|
is_fixed_arr_ret = true
|
||||||
} else {
|
} else {
|
||||||
str_dumparg_ret_type = str_dumparg_type
|
str_dumparg_ret_type = str_dumparg_type
|
||||||
}
|
}
|
||||||
|
@ -173,7 +190,7 @@ fn (mut g Gen) dump_expr_definitions() {
|
||||||
dump_fns.writeln('\tstrings__Builder_write_string(&sb, value);')
|
dump_fns.writeln('\tstrings__Builder_write_string(&sb, value);')
|
||||||
dump_fns.writeln("\tstrings__Builder_write_rune(&sb, '\\n');")
|
dump_fns.writeln("\tstrings__Builder_write_rune(&sb, '\\n');")
|
||||||
surrounder.builder_write_afters(mut dump_fns)
|
surrounder.builder_write_afters(mut dump_fns)
|
||||||
if !typ.has_flag(.option) && dump_sym.kind == .array_fixed {
|
if is_fixed_arr_ret {
|
||||||
tmp_var := g.new_tmp_var()
|
tmp_var := g.new_tmp_var()
|
||||||
dump_fns.writeln('\t${str_dumparg_ret_type} ${tmp_var} = {0};')
|
dump_fns.writeln('\t${str_dumparg_ret_type} ${tmp_var} = {0};')
|
||||||
dump_fns.writeln('\tmemcpy(${tmp_var}.ret_arr, dump_arg, sizeof(${str_dumparg_type}));')
|
dump_fns.writeln('\tmemcpy(${tmp_var}.ret_arr, dump_arg, sizeof(${str_dumparg_type}));')
|
||||||
|
|
|
@ -225,8 +225,16 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
||||||
mut name := g.c_fn_name(node)
|
mut name := g.c_fn_name(node)
|
||||||
mut type_name := g.typ(g.unwrap_generic(node.return_type))
|
mut type_name := g.typ(g.unwrap_generic(node.return_type))
|
||||||
|
|
||||||
if node.return_type.has_flag(.generic) && g.table.sym(node.return_type).kind == .array_fixed {
|
ret_sym := g.table.sym(node.return_type)
|
||||||
|
if node.return_type.has_flag(.generic) && ret_sym.kind == .array_fixed {
|
||||||
type_name = '_v_${type_name}'
|
type_name = '_v_${type_name}'
|
||||||
|
} else if ret_sym.kind == .alias && !node.return_type.has_flag(.option) {
|
||||||
|
unalias_typ := g.table.unaliased_type(node.return_type)
|
||||||
|
unalias_sym := g.table.sym(unalias_typ)
|
||||||
|
if unalias_sym.kind == .array_fixed {
|
||||||
|
type_name = if !(unalias_sym.info as ast.ArrayFixed).is_fn_ret { '_v_' } else { '' } +
|
||||||
|
g.typ(unalias_typ)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main
|
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main
|
||||||
|
@ -683,9 +691,12 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
||||||
tmp_var := g.new_tmp_var()
|
tmp_var := g.new_tmp_var()
|
||||||
fn_type := g.fn_var_signature(node.left.decl.return_type, node.left.decl.params.map(it.typ),
|
fn_type := g.fn_var_signature(node.left.decl.return_type, node.left.decl.params.map(it.typ),
|
||||||
tmp_var)
|
tmp_var)
|
||||||
|
line := g.go_before_stmt(0).trim_space()
|
||||||
|
g.empty_line = true
|
||||||
g.write('${fn_type} = ')
|
g.write('${fn_type} = ')
|
||||||
g.expr(node.left)
|
g.expr(node.left)
|
||||||
g.writeln(';')
|
g.writeln(';')
|
||||||
|
g.write(line)
|
||||||
g.write(tmp_var)
|
g.write(tmp_var)
|
||||||
} else if node.or_block.kind == .absent {
|
} else if node.or_block.kind == .absent {
|
||||||
g.expr(node.left)
|
g.expr(node.left)
|
||||||
|
@ -754,7 +765,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
||||||
ret_typ = unaliased_type
|
ret_typ = unaliased_type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styp := g.typ(ret_typ)
|
mut styp := g.typ(ret_typ)
|
||||||
if gen_or && !is_gen_or_and_assign_rhs {
|
if gen_or && !is_gen_or_and_assign_rhs {
|
||||||
cur_line = g.go_before_stmt(0)
|
cur_line = g.go_before_stmt(0)
|
||||||
}
|
}
|
||||||
|
@ -764,6 +775,9 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
||||||
g.indent++
|
g.indent++
|
||||||
g.write('${tmp_opt} = ')
|
g.write('${tmp_opt} = ')
|
||||||
} else if !g.inside_curry_call {
|
} else if !g.inside_curry_call {
|
||||||
|
if g.assign_ct_type != 0 && node.or_block.kind in [.propagate_option, .propagate_result] {
|
||||||
|
styp = g.typ(g.assign_ct_type.derive(ret_typ))
|
||||||
|
}
|
||||||
g.write('${styp} ${tmp_opt} = ')
|
g.write('${styp} ${tmp_opt} = ')
|
||||||
if node.left is ast.AnonFn {
|
if node.left is ast.AnonFn {
|
||||||
g.expr(node.left)
|
g.expr(node.left)
|
||||||
|
@ -1281,7 +1295,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
|
||||||
receiver_type_name = 'map'
|
receiver_type_name = 'map'
|
||||||
}
|
}
|
||||||
if final_left_sym.kind == .array && !(left_sym.kind == .alias && left_sym.has_method(node.name))
|
if final_left_sym.kind == .array && !(left_sym.kind == .alias && left_sym.has_method(node.name))
|
||||||
&& node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
|
&& node.name in ['clear', 'repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
|
||||||
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
|
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
|
||||||
// `array_Xyz_clone` => `array_clone`
|
// `array_Xyz_clone` => `array_clone`
|
||||||
receiver_type_name = 'array'
|
receiver_type_name = 'array'
|
||||||
|
|
|
@ -35,8 +35,17 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
||||||
g.infix_expr_arithmetic_op(node)
|
g.infix_expr_arithmetic_op(node)
|
||||||
}
|
}
|
||||||
.left_shift {
|
.left_shift {
|
||||||
|
// `a << b` can mean many things in V ...
|
||||||
|
// TODO: disambiguate everything in the checker; cgen should not decide all this.
|
||||||
|
// Instead it should be as simple, as the branch for .right_shift is.
|
||||||
|
// `array << val` should have its own separate operation internally.
|
||||||
g.infix_expr_left_shift_op(node)
|
g.infix_expr_left_shift_op(node)
|
||||||
}
|
}
|
||||||
|
.right_shift {
|
||||||
|
g.write('(')
|
||||||
|
g.gen_plain_infix_expr(node)
|
||||||
|
g.write(')')
|
||||||
|
}
|
||||||
.and, .logical_or {
|
.and, .logical_or {
|
||||||
g.infix_expr_and_or_op(node)
|
g.infix_expr_and_or_op(node)
|
||||||
}
|
}
|
||||||
|
@ -846,7 +855,9 @@ fn (mut g Gen) infix_expr_left_shift_op(node ast.InfixExpr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
g.write('(')
|
||||||
g.gen_plain_infix_expr(node)
|
g.gen_plain_infix_expr(node)
|
||||||
|
g.write(')')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,9 @@ fn (mut g Gen) gen_jsons() {
|
||||||
g.expr_with_tmp_var(ast.Expr(ast.StructInit{ typ: utyp, typ_str: styp }),
|
g.expr_with_tmp_var(ast.Expr(ast.StructInit{ typ: utyp, typ_str: styp }),
|
||||||
utyp, utyp, 'res')
|
utyp, utyp, 'res')
|
||||||
init_styp = g.out.cut_to(pos).trim_space()
|
init_styp = g.out.cut_to(pos).trim_space()
|
||||||
|
} else {
|
||||||
|
none_str := g.expr_string(ast.None{})
|
||||||
|
init_styp += ' = (${styp}){ .state=2, .err=${none_str}, .data={EMPTY_STRUCT_INITIALIZATION} }'
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if sym.kind == .struct_ {
|
if sym.kind == .struct_ {
|
||||||
|
@ -148,8 +151,13 @@ ${enc_fn_dec} {
|
||||||
parent_typ := a.parent_type
|
parent_typ := a.parent_type
|
||||||
psym := g.table.sym(parent_typ)
|
psym := g.table.sym(parent_typ)
|
||||||
if is_js_prim(g.typ(parent_typ)) {
|
if is_js_prim(g.typ(parent_typ)) {
|
||||||
g.gen_json_for_type(parent_typ)
|
if utyp.has_flag(.option) {
|
||||||
g.gen_prim_enc_dec(parent_typ, mut enc, mut dec)
|
g.gen_json_for_type(parent_typ.set_flag(.option))
|
||||||
|
g.gen_option_enc_dec(parent_typ.set_flag(.option), mut enc, mut dec)
|
||||||
|
} else {
|
||||||
|
g.gen_json_for_type(parent_typ)
|
||||||
|
g.gen_prim_enc_dec(parent_typ, mut enc, mut dec)
|
||||||
|
}
|
||||||
} else if psym.info is ast.Struct {
|
} else if psym.info is ast.Struct {
|
||||||
enc.writeln('\to = cJSON_CreateObject();')
|
enc.writeln('\to = cJSON_CreateObject();')
|
||||||
g.gen_struct_enc_dec(utyp, psym.info, ret_styp, mut enc, mut dec)
|
g.gen_struct_enc_dec(utyp, psym.info, ret_styp, mut enc, mut dec)
|
||||||
|
@ -163,6 +171,8 @@ ${enc_fn_dec} {
|
||||||
g.gen_json_for_type(m.value_type)
|
g.gen_json_for_type(m.value_type)
|
||||||
dec.writeln(g.decode_map(utyp, m.key_type, m.value_type, ret_styp))
|
dec.writeln(g.decode_map(utyp, m.key_type, m.value_type, ret_styp))
|
||||||
enc.writeln(g.encode_map(utyp, m.key_type, m.value_type))
|
enc.writeln(g.encode_map(utyp, m.key_type, m.value_type))
|
||||||
|
} else if utyp.has_flag(.option) {
|
||||||
|
g.gen_option_enc_dec(utyp, mut enc, mut dec)
|
||||||
} else {
|
} else {
|
||||||
verror('json: ${sym.name} is not struct')
|
verror('json: ${sym.name} is not struct')
|
||||||
}
|
}
|
||||||
|
@ -190,7 +200,9 @@ ${enc_fn_dec} {
|
||||||
dec.writeln('\t${result_name}_${ret_styp} ret;')
|
dec.writeln('\t${result_name}_${ret_styp} ret;')
|
||||||
dec.writeln('\t_result_ok(&res, (${result_name}*)&ret, sizeof(res));')
|
dec.writeln('\t_result_ok(&res, (${result_name}*)&ret, sizeof(res));')
|
||||||
if utyp.has_flag(.option) {
|
if utyp.has_flag(.option) {
|
||||||
dec.writeln('\t_option_ok(&res.data, (${option_name}*)&ret.data, sizeof(${g.base_type(utyp)}));')
|
dec.writeln('\tif (res.state != 2) {')
|
||||||
|
dec.writeln('\t\t_option_ok(&res.data, (${option_name}*)&ret.data, sizeof(${g.base_type(utyp)}));')
|
||||||
|
dec.writeln('\t}')
|
||||||
}
|
}
|
||||||
dec.writeln('\treturn ret;\n}')
|
dec.writeln('\treturn ret;\n}')
|
||||||
enc.writeln('\treturn o;\n}')
|
enc.writeln('\treturn o;\n}')
|
||||||
|
@ -332,7 +344,11 @@ fn (mut g Gen) gen_option_enc_dec(typ ast.Type, mut enc strings.Builder, mut dec
|
||||||
enc.writeln('\to = ${encode_name}(*(${type_str}*)val.data);')
|
enc.writeln('\to = ${encode_name}(*(${type_str}*)val.data);')
|
||||||
|
|
||||||
dec_name := js_dec_name(type_str)
|
dec_name := js_dec_name(type_str)
|
||||||
dec.writeln('\t_option_ok(&(${type_str}[]){ ${dec_name}(root) }, (${option_name}*)&res, sizeof(${type_str}));')
|
dec.writeln('\tif (!cJSON_IsNull(root)) {')
|
||||||
|
dec.writeln('\t\t_option_ok(&(${type_str}[]){ ${dec_name}(root) }, (${option_name}*)&res, sizeof(${type_str}));')
|
||||||
|
dec.writeln('\t} else {')
|
||||||
|
dec.writeln('\t\t_option_none(&(${type_str}[]){ {0} }, (${option_name}*)&res, sizeof(${type_str}));')
|
||||||
|
dec.writeln('\t}')
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
|
@ -630,6 +646,9 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
|
||||||
tmp := g.new_tmp_var()
|
tmp := g.new_tmp_var()
|
||||||
gen_js_get(styp, tmp, name, mut dec, is_required)
|
gen_js_get(styp, tmp, name, mut dec, is_required)
|
||||||
dec.writeln('\tif (jsonroot_${tmp}) {')
|
dec.writeln('\tif (jsonroot_${tmp}) {')
|
||||||
|
if utyp.has_flag(.option) {
|
||||||
|
dec.writeln('\t\tres.state = 0;')
|
||||||
|
}
|
||||||
dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${dec_name}(jsonroot_${tmp});')
|
dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${dec_name}(jsonroot_${tmp});')
|
||||||
if field.has_default_expr {
|
if field.has_default_expr {
|
||||||
dec.writeln('\t} else {')
|
dec.writeln('\t} else {')
|
||||||
|
@ -684,9 +703,14 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
|
||||||
dec.writeln('\t}')
|
dec.writeln('\t}')
|
||||||
} else if field_sym.kind == .alias {
|
} else if field_sym.kind == .alias {
|
||||||
alias := field_sym.info as ast.Alias
|
alias := field_sym.info as ast.Alias
|
||||||
parent_type := g.typ(alias.parent_type)
|
parent_type := if field.typ.has_flag(.option) {
|
||||||
parent_dec_name := js_dec_name(parent_type)
|
alias.parent_type.set_flag(.option)
|
||||||
if is_js_prim(parent_type) {
|
} else {
|
||||||
|
alias.parent_type
|
||||||
|
}
|
||||||
|
sparent_type := g.typ(parent_type)
|
||||||
|
parent_dec_name := js_dec_name(sparent_type)
|
||||||
|
if is_js_prim(sparent_type) {
|
||||||
tmp := g.new_tmp_var()
|
tmp := g.new_tmp_var()
|
||||||
gen_js_get(styp, tmp, name, mut dec, is_required)
|
gen_js_get(styp, tmp, name, mut dec, is_required)
|
||||||
dec.writeln('\tif (jsonroot_${tmp}) {')
|
dec.writeln('\tif (jsonroot_${tmp}) {')
|
||||||
|
@ -697,7 +721,7 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
|
||||||
}
|
}
|
||||||
dec.writeln('\t}')
|
dec.writeln('\t}')
|
||||||
} else {
|
} else {
|
||||||
g.gen_json_for_type(alias.parent_type)
|
g.gen_json_for_type(parent_type)
|
||||||
tmp := g.new_tmp_var()
|
tmp := g.new_tmp_var()
|
||||||
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
|
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
|
||||||
dec.writeln('\tif (jsonroot_${tmp}) {')
|
dec.writeln('\tif (jsonroot_${tmp}) {')
|
||||||
|
@ -772,7 +796,11 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
|
||||||
if !is_js_prim(field_type) {
|
if !is_js_prim(field_type) {
|
||||||
if field_sym.kind == .alias {
|
if field_sym.kind == .alias {
|
||||||
ainfo := field_sym.info as ast.Alias
|
ainfo := field_sym.info as ast.Alias
|
||||||
enc_name = js_enc_name(g.typ(ainfo.parent_type))
|
if field.typ.has_flag(.option) {
|
||||||
|
enc_name = js_enc_name(g.typ(ainfo.parent_type.set_flag(.option)))
|
||||||
|
} else {
|
||||||
|
enc_name = js_enc_name(g.typ(ainfo.parent_type))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if field_sym.kind == .enum_ {
|
if field_sym.kind == .enum_ {
|
||||||
|
@ -803,19 +831,24 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
|
||||||
if field_sym.name == 'time.Time' {
|
if field_sym.name == 'time.Time' {
|
||||||
// time struct requires special treatment
|
// time struct requires special treatment
|
||||||
// it has to be encoded as a unix timestamp number
|
// it has to be encoded as a unix timestamp number
|
||||||
enc.writeln('${indent}\tcJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}._v_unix));')
|
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}._v_unix));')
|
||||||
} else {
|
} else {
|
||||||
if !field.typ.is_any_kind_of_pointer() {
|
if !field.typ.is_any_kind_of_pointer() {
|
||||||
enc.writeln('${indent}\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${prefix_enc}${op}${c_name(field.name)})); /*A*/')
|
if field_sym.kind == .alias && field.typ.has_flag(.option) {
|
||||||
|
parent_type := g.table.unaliased_type(field.typ).set_flag(.option)
|
||||||
|
enc.writeln('${indent}\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(*(${g.typ(parent_type)}*)&${prefix_enc}${op}${c_name(field.name)})); /*?A*/')
|
||||||
|
} else {
|
||||||
|
enc.writeln('${indent}\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${prefix_enc}${op}${c_name(field.name)})); /*A*/')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
arg_prefix := if field.typ.is_ptr() { '' } else { '*' }
|
arg_prefix := if field.typ.is_ptr() { '' } else { '*' }
|
||||||
sptr_value := '${prefix_enc}${op}${c_name(field.name)}'
|
sptr_value := '${prefix_enc}${op}${c_name(field.name)}'
|
||||||
if !field.typ.has_flag(.option) {
|
if !field.typ.has_flag(.option) {
|
||||||
enc.writeln('${indent}\tif (${sptr_value} != 0) {')
|
enc.writeln('${indent}if (${sptr_value} != 0) {')
|
||||||
enc.writeln('${indent}\t\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${arg_prefix}${sptr_value}));')
|
enc.writeln('${indent}\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${arg_prefix}${sptr_value}));')
|
||||||
enc.writeln('${indent}\t}\n')
|
enc.writeln('${indent}}\n')
|
||||||
} else {
|
} else {
|
||||||
enc.writeln('${indent}\t\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${arg_prefix}${sptr_value}));')
|
enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", ${enc_name}(${arg_prefix}${sptr_value}));')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,9 +70,8 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) {
|
||||||
if (node.cond in [ast.Ident, ast.IntegerLiteral, ast.StringLiteral, ast.FloatLiteral]
|
if (node.cond in [ast.Ident, ast.IntegerLiteral, ast.StringLiteral, ast.FloatLiteral]
|
||||||
&& (node.cond !is ast.Ident || (node.cond is ast.Ident
|
&& (node.cond !is ast.Ident || (node.cond is ast.Ident
|
||||||
&& node.cond.or_expr.kind == .absent))) || (node.cond is ast.SelectorExpr
|
&& node.cond.or_expr.kind == .absent))) || (node.cond is ast.SelectorExpr
|
||||||
&& node.cond.or_block.kind == .absent
|
&& node.cond.or_block.kind == .absent && (node.cond.expr !is ast.CallExpr
|
||||||
&& ((node.cond as ast.SelectorExpr).expr !is ast.CallExpr
|
|| (node.cond.expr as ast.CallExpr).or_block.kind == .absent)) {
|
||||||
|| ((node.cond as ast.SelectorExpr).expr as ast.CallExpr).or_block.kind == .absent)) {
|
|
||||||
cond_var = g.expr_string(node.cond)
|
cond_var = g.expr_string(node.cond)
|
||||||
} else {
|
} else {
|
||||||
line := if is_expr {
|
line := if is_expr {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,91 +0,0 @@
|
||||||
module wasm
|
|
||||||
|
|
||||||
import v.ast
|
|
||||||
import v.gen.wasm.binaryen
|
|
||||||
|
|
||||||
fn (mut g Gen) is_signed(typ ast.Type) bool {
|
|
||||||
if typ.is_pure_float() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return typ.is_signed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) unary_cast(from binaryen.Type, is_signed bool, to binaryen.Type) binaryen.Op {
|
|
||||||
if is_signed {
|
|
||||||
match from {
|
|
||||||
type_i32 {
|
|
||||||
match to {
|
|
||||||
type_i64 { return binaryen.extendsint32() }
|
|
||||||
type_f32 { return binaryen.convertsint32tofloat32() }
|
|
||||||
type_f64 { return binaryen.convertsint32tofloat64() }
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_i64 {
|
|
||||||
match to {
|
|
||||||
type_i32 { return binaryen.wrapint64() }
|
|
||||||
type_f32 { return binaryen.convertsint64tofloat32() }
|
|
||||||
type_f64 { return binaryen.convertsint64tofloat64() }
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_f32 {
|
|
||||||
match to {
|
|
||||||
type_i32 { return binaryen.truncsfloat32toint32() }
|
|
||||||
type_i64 { return binaryen.truncsfloat32toint64() }
|
|
||||||
type_f64 { return binaryen.promotefloat32() }
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_f64 {
|
|
||||||
match to {
|
|
||||||
type_i32 { return binaryen.truncsfloat64toint32() }
|
|
||||||
type_i64 { return binaryen.truncsfloat64toint64() }
|
|
||||||
type_f32 { return binaryen.demotefloat64() }
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match from {
|
|
||||||
type_i32 {
|
|
||||||
match to {
|
|
||||||
type_i64 { return binaryen.extenduint32() }
|
|
||||||
type_f32 { return binaryen.convertuint32tofloat32() }
|
|
||||||
type_f64 { return binaryen.convertuint32tofloat64() }
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_i64 {
|
|
||||||
match to {
|
|
||||||
type_i32 { return binaryen.wrapint64() }
|
|
||||||
type_f32 { return binaryen.convertuint64tofloat32() }
|
|
||||||
type_f64 { return binaryen.convertuint64tofloat64() }
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g.w_error('bad cast: from ${from} (is signed: ${is_signed}) to ${to}')
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) cast_t(expr binaryen.Expression, from ast.Type, to ast.Type) binaryen.Expression {
|
|
||||||
return g.cast(expr, g.get_wasm_type(from), g.is_signed(from), g.get_wasm_type(to))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) cast(expr binaryen.Expression, from binaryen.Type, is_signed bool, to binaryen.Type) binaryen.Expression {
|
|
||||||
if from == to {
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the official spec, integers are represented in twos complement.
|
|
||||||
// WebAssembly does not keep signedness information in it's types
|
|
||||||
// and uses instructions with variants for signed or unsigned values.
|
|
||||||
//
|
|
||||||
// You only need to know if the original type is signed or not to
|
|
||||||
// perform casting.
|
|
||||||
|
|
||||||
return binaryen.unary(g.mod, g.unary_cast(from, is_signed, to), expr)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,69 +1,73 @@
|
||||||
|
// Copyright (c) 2023 l-m.dev. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module wasm
|
module wasm
|
||||||
|
|
||||||
import v.ast
|
import v.ast
|
||||||
import v.token
|
import v.token
|
||||||
import v.gen.wasm.binaryen
|
import wasm
|
||||||
|
|
||||||
const (
|
pub fn (mut g Gen) as_numtype(a wasm.ValType) wasm.NumType {
|
||||||
type_none = binaryen.typenone()
|
if a in [.funcref_t, .externref_t, .v128_t] {
|
||||||
type_auto = binaryen.typeauto()
|
g.w_error("as_numtype: called with '${a}'")
|
||||||
type_i32 = binaryen.typeint32()
|
}
|
||||||
type_i64 = binaryen.typeint64()
|
|
||||||
type_f32 = binaryen.typefloat32()
|
return unsafe { wasm.NumType(a) }
|
||||||
type_f64 = binaryen.typefloat64()
|
}
|
||||||
)
|
|
||||||
|
// unwraps int_literal to i64_t
|
||||||
|
pub fn (mut g Gen) get_wasm_type_int_literal(typ_ ast.Type) wasm.ValType {
|
||||||
|
if typ_ == ast.int_literal_type_idx {
|
||||||
|
return wasm.ValType.i64_t
|
||||||
|
}
|
||||||
|
return g.get_wasm_type(typ_)
|
||||||
|
}
|
||||||
|
|
||||||
// "Register size" types such as int, i64 and bool boil down to their WASM counterparts.
|
// "Register size" types such as int, i64 and bool boil down to their WASM counterparts.
|
||||||
// Structures and unions are pointers, i32.
|
// Structures and unions are pointers, i32.
|
||||||
fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type {
|
pub fn (mut g Gen) get_wasm_type(typ_ ast.Type) wasm.ValType {
|
||||||
typ := ast.mktyp(typ_)
|
typ := ast.mktyp(typ_)
|
||||||
if typ == ast.void_type_idx {
|
if typ == ast.void_type_idx {
|
||||||
return wasm.type_none
|
g.w_error("get_wasm_type: called with 'void'")
|
||||||
}
|
}
|
||||||
if typ.is_any_kind_of_pointer() {
|
if typ.is_ptr() || typ.is_pointer() {
|
||||||
g.needs_stack = true
|
return wasm.ValType.i32_t
|
||||||
return wasm.type_i32
|
|
||||||
}
|
}
|
||||||
if typ in ast.number_type_idxs {
|
if typ in ast.number_type_idxs {
|
||||||
return match typ {
|
return match typ {
|
||||||
ast.isize_type_idx, ast.usize_type_idx, ast.i8_type_idx, ast.u8_type_idx,
|
ast.isize_type_idx, ast.usize_type_idx, ast.i8_type_idx, ast.u8_type_idx,
|
||||||
ast.char_type_idx, ast.rune_type_idx, ast.i16_type_idx, ast.u16_type_idx,
|
ast.char_type_idx, ast.rune_type_idx, ast.i16_type_idx, ast.u16_type_idx,
|
||||||
ast.int_type_idx, ast.u32_type_idx {
|
ast.int_type_idx, ast.u32_type_idx {
|
||||||
wasm.type_i32
|
wasm.ValType.i32_t
|
||||||
}
|
}
|
||||||
ast.i64_type_idx, ast.u64_type_idx, ast.int_literal_type_idx {
|
ast.i64_type_idx, ast.u64_type_idx {
|
||||||
wasm.type_i64
|
wasm.ValType.i64_t
|
||||||
}
|
}
|
||||||
ast.f32_type_idx {
|
ast.f32_type_idx {
|
||||||
wasm.type_f32
|
wasm.ValType.f32_t
|
||||||
}
|
}
|
||||||
ast.f64_type_idx, ast.float_literal_type_idx {
|
ast.f64_type_idx {
|
||||||
wasm.type_f64
|
wasm.ValType.f64_t
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wasm.type_i32
|
wasm.ValType.i32_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if typ == ast.bool_type_idx {
|
if typ == ast.bool_type_idx {
|
||||||
return wasm.type_i32
|
return wasm.ValType.i32_t
|
||||||
}
|
}
|
||||||
ts := g.table.sym(typ)
|
ts := g.table.sym(typ)
|
||||||
match ts.info {
|
match ts.info {
|
||||||
ast.Struct {
|
ast.Struct {
|
||||||
g.get_type_size_align(typ)
|
g.pool.type_size(typ)
|
||||||
return wasm.type_i32 // pointer
|
return wasm.ValType.i32_t // pointer
|
||||||
}
|
|
||||||
ast.MultiReturn {
|
|
||||||
// TODO: cache??
|
|
||||||
mut paraml := ts.info.types.map(g.get_wasm_type(it))
|
|
||||||
return binaryen.typecreate(paraml.data, paraml.len)
|
|
||||||
}
|
}
|
||||||
ast.Alias {
|
ast.Alias {
|
||||||
return g.get_wasm_type(ts.info.parent_type)
|
return g.get_wasm_type(ts.info.parent_type)
|
||||||
}
|
}
|
||||||
ast.ArrayFixed {
|
ast.ArrayFixed {
|
||||||
return wasm.type_i32
|
return wasm.ValType.i32_t // pointer
|
||||||
}
|
}
|
||||||
ast.Enum {
|
ast.Enum {
|
||||||
return g.get_wasm_type(ts.info.typ)
|
return g.get_wasm_type(ts.info.typ)
|
||||||
|
@ -74,251 +78,68 @@ fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type {
|
||||||
g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}")
|
g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infix_kind_return_bool(op token.Kind) bool {
|
pub fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) {
|
||||||
return op in [.eq, .ne, .gt, .lt, .ge, .le]
|
if g.is_param_type(typ) {
|
||||||
}
|
eprintln(*g.table.sym(typ))
|
||||||
|
panic('unimplemented')
|
||||||
fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) binaryen.Op {
|
}
|
||||||
wasm_typ := g.get_wasm_type(typ)
|
|
||||||
|
wasm_typ := g.as_numtype(g.get_wasm_type(typ))
|
||||||
match wasm_typ {
|
|
||||||
wasm.type_i32 {
|
match op {
|
||||||
match op {
|
.plus {
|
||||||
.plus {
|
g.func.add(wasm_typ)
|
||||||
return binaryen.addint32()
|
}
|
||||||
}
|
.minus {
|
||||||
.minus {
|
g.func.sub(wasm_typ)
|
||||||
return binaryen.subint32()
|
}
|
||||||
}
|
.mul {
|
||||||
.mul {
|
g.func.mul(wasm_typ)
|
||||||
return binaryen.mulint32()
|
}
|
||||||
}
|
.mod {
|
||||||
.mod {
|
g.func.rem(wasm_typ, typ.is_signed())
|
||||||
if typ.is_signed() {
|
}
|
||||||
return binaryen.remsint32()
|
.div {
|
||||||
} else {
|
g.func.div(wasm_typ, typ.is_signed())
|
||||||
return binaryen.remuint32()
|
}
|
||||||
}
|
.eq {
|
||||||
}
|
g.func.eq(wasm_typ)
|
||||||
.div {
|
}
|
||||||
if typ.is_signed() {
|
.ne {
|
||||||
return binaryen.divsint32()
|
g.func.ne(wasm_typ)
|
||||||
} else {
|
}
|
||||||
return binaryen.divuint32()
|
.gt {
|
||||||
}
|
g.func.gt(wasm_typ, typ.is_signed())
|
||||||
}
|
}
|
||||||
.eq {
|
.lt {
|
||||||
return binaryen.eqint32()
|
g.func.lt(wasm_typ, typ.is_signed())
|
||||||
}
|
}
|
||||||
.ne {
|
.ge {
|
||||||
return binaryen.neint32()
|
g.func.ge(wasm_typ, typ.is_signed())
|
||||||
}
|
}
|
||||||
.gt {
|
.le {
|
||||||
if typ.is_signed() {
|
g.func.le(wasm_typ, typ.is_signed())
|
||||||
return binaryen.gtsint32()
|
}
|
||||||
} else {
|
.xor {
|
||||||
return binaryen.gtuint32()
|
g.func.b_xor(wasm_typ)
|
||||||
}
|
}
|
||||||
}
|
.pipe {
|
||||||
.lt {
|
g.func.b_or(wasm_typ)
|
||||||
if typ.is_signed() {
|
}
|
||||||
return binaryen.ltsint32()
|
.amp {
|
||||||
} else {
|
g.func.b_and(wasm_typ)
|
||||||
return binaryen.ltuint32()
|
}
|
||||||
}
|
.left_shift {
|
||||||
}
|
g.func.b_shl(wasm_typ)
|
||||||
.ge {
|
}
|
||||||
if typ.is_signed() {
|
.right_shift {
|
||||||
return binaryen.gesint32()
|
g.func.b_shr(wasm_typ, true)
|
||||||
} else {
|
}
|
||||||
return binaryen.geuint32()
|
.unsigned_right_shift {
|
||||||
}
|
g.func.b_shr(wasm_typ, false)
|
||||||
}
|
}
|
||||||
.le {
|
else {
|
||||||
if typ.is_signed() {
|
g.w_error('bad infix: op `${op}`')
|
||||||
return binaryen.lesint32()
|
}
|
||||||
} else {
|
|
||||||
return binaryen.leuint32()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*.logical_or {
|
|
||||||
return binaryen.orint32() // TODO: logical or
|
|
||||||
}*/
|
|
||||||
.xor {
|
|
||||||
return binaryen.xorint32()
|
|
||||||
}
|
|
||||||
.pipe {
|
|
||||||
return binaryen.orint32()
|
|
||||||
}
|
|
||||||
.amp {
|
|
||||||
return binaryen.andint32()
|
|
||||||
}
|
|
||||||
.left_shift {
|
|
||||||
return binaryen.shlint32()
|
|
||||||
}
|
|
||||||
.right_shift {
|
|
||||||
return binaryen.shrsint32()
|
|
||||||
}
|
|
||||||
.unsigned_right_shift {
|
|
||||||
return binaryen.shruint32()
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wasm.type_i64 {
|
|
||||||
match op {
|
|
||||||
.plus {
|
|
||||||
return binaryen.addint64()
|
|
||||||
}
|
|
||||||
.minus {
|
|
||||||
return binaryen.subint64()
|
|
||||||
}
|
|
||||||
.mul {
|
|
||||||
return binaryen.mulint64()
|
|
||||||
}
|
|
||||||
.mod {
|
|
||||||
if typ.is_signed() {
|
|
||||||
return binaryen.remsint64()
|
|
||||||
} else {
|
|
||||||
return binaryen.remuint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.div {
|
|
||||||
if typ.is_signed() {
|
|
||||||
return binaryen.divsint64()
|
|
||||||
} else {
|
|
||||||
return binaryen.divuint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.eq {
|
|
||||||
return binaryen.eqint64()
|
|
||||||
}
|
|
||||||
.ne {
|
|
||||||
return binaryen.neint64()
|
|
||||||
}
|
|
||||||
.gt {
|
|
||||||
if typ.is_signed() {
|
|
||||||
return binaryen.gtsint64()
|
|
||||||
} else {
|
|
||||||
return binaryen.gtuint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lt {
|
|
||||||
if typ.is_signed() {
|
|
||||||
return binaryen.ltsint64()
|
|
||||||
} else {
|
|
||||||
return binaryen.ltuint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ge {
|
|
||||||
if typ.is_signed() {
|
|
||||||
return binaryen.gesint64()
|
|
||||||
} else {
|
|
||||||
return binaryen.geuint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.le {
|
|
||||||
if typ.is_signed() {
|
|
||||||
return binaryen.lesint64()
|
|
||||||
} else {
|
|
||||||
return binaryen.leuint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*.logical_or {
|
|
||||||
return binaryen.orint64() // TODO: logical or
|
|
||||||
}*/
|
|
||||||
.xor {
|
|
||||||
return binaryen.xorint64()
|
|
||||||
}
|
|
||||||
.pipe {
|
|
||||||
return binaryen.orint64()
|
|
||||||
}
|
|
||||||
.amp {
|
|
||||||
return binaryen.andint64()
|
|
||||||
}
|
|
||||||
.left_shift {
|
|
||||||
return binaryen.shlint64()
|
|
||||||
}
|
|
||||||
.right_shift {
|
|
||||||
return binaryen.shrsint64()
|
|
||||||
}
|
|
||||||
.unsigned_right_shift {
|
|
||||||
return binaryen.shruint64()
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wasm.type_f32 {
|
|
||||||
match op {
|
|
||||||
.plus {
|
|
||||||
return binaryen.addfloat32()
|
|
||||||
}
|
|
||||||
.minus {
|
|
||||||
return binaryen.subfloat32()
|
|
||||||
}
|
|
||||||
.mul {
|
|
||||||
return binaryen.mulfloat32()
|
|
||||||
}
|
|
||||||
.div {
|
|
||||||
return binaryen.divfloat32()
|
|
||||||
}
|
|
||||||
.eq {
|
|
||||||
return binaryen.eqfloat32()
|
|
||||||
}
|
|
||||||
.ne {
|
|
||||||
return binaryen.nefloat32()
|
|
||||||
}
|
|
||||||
.gt {
|
|
||||||
return binaryen.gtfloat32()
|
|
||||||
}
|
|
||||||
.lt {
|
|
||||||
return binaryen.ltfloat32()
|
|
||||||
}
|
|
||||||
.ge {
|
|
||||||
return binaryen.gefloat32()
|
|
||||||
}
|
|
||||||
.le {
|
|
||||||
return binaryen.lefloat32()
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wasm.type_f64 {
|
|
||||||
match op {
|
|
||||||
.plus {
|
|
||||||
return binaryen.addfloat64()
|
|
||||||
}
|
|
||||||
.minus {
|
|
||||||
return binaryen.subfloat64()
|
|
||||||
}
|
|
||||||
.mul {
|
|
||||||
return binaryen.mulfloat64()
|
|
||||||
}
|
|
||||||
.div {
|
|
||||||
return binaryen.divfloat64()
|
|
||||||
}
|
|
||||||
.eq {
|
|
||||||
return binaryen.eqfloat64()
|
|
||||||
}
|
|
||||||
.ne {
|
|
||||||
return binaryen.nefloat64()
|
|
||||||
}
|
|
||||||
.gt {
|
|
||||||
return binaryen.gtfloat64()
|
|
||||||
}
|
|
||||||
.lt {
|
|
||||||
return binaryen.ltfloat64()
|
|
||||||
}
|
|
||||||
.ge {
|
|
||||||
return binaryen.gefloat64()
|
|
||||||
}
|
|
||||||
.le {
|
|
||||||
return binaryen.lefloat64()
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
}
|
||||||
g.w_error('bad infix: op `${op}`')
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
module wasm
|
|
||||||
|
|
||||||
import v.ast
|
|
||||||
import v.gen.wasm.binaryen
|
|
||||||
import encoding.binary as bin
|
|
||||||
import math.bits
|
|
||||||
|
|
||||||
fn (mut g Gen) bake_constants_plus_initialisers() []GlobalData {
|
|
||||||
mut initialisers := []GlobalData{}
|
|
||||||
|
|
||||||
for _, global in g.globals {
|
|
||||||
match global.init {
|
|
||||||
/*
|
|
||||||
ast.ArrayInit {
|
|
||||||
// TODO: call a seraliser recursively over all elements
|
|
||||||
|
|
||||||
if !global.init.is_fixed {
|
|
||||||
g.w_error('wasm backend does not support non fixed arrays yet')
|
|
||||||
}
|
|
||||||
for global.init
|
|
||||||
}*/
|
|
||||||
ast.BoolLiteral {
|
|
||||||
g.constant_data << ConstantData{
|
|
||||||
offset: global.abs_address
|
|
||||||
data: [u8(global.init.val)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast.FloatLiteral {
|
|
||||||
mut buf := []u8{len: 8}
|
|
||||||
wtyp := g.get_wasm_type(global.ast_typ)
|
|
||||||
match wtyp {
|
|
||||||
type_f32 {
|
|
||||||
bin.little_endian_put_u32(mut buf, bits.f32_bits(global.init.val.f32()))
|
|
||||||
}
|
|
||||||
type_f64 {
|
|
||||||
bin.little_endian_put_u64(mut buf, bits.f64_bits(global.init.val.f64()))
|
|
||||||
unsafe {
|
|
||||||
buf.len = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.constant_data << ConstantData{
|
|
||||||
offset: global.abs_address
|
|
||||||
data: buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast.StringLiteral {
|
|
||||||
offset, len := g.allocate_string(global.init)
|
|
||||||
|
|
||||||
if g.table.sym(global.ast_typ).info !is ast.Struct {
|
|
||||||
mut buf := []u8{len: 4}
|
|
||||||
bin.little_endian_put_u32(mut buf, u32(offset))
|
|
||||||
g.constant_data << ConstantData{
|
|
||||||
offset: global.abs_address
|
|
||||||
data: buf
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mut buf := []u8{len: 8}
|
|
||||||
bin.little_endian_put_u32(mut buf, u32(offset))
|
|
||||||
bin.little_endian_put_u32_at(mut buf, u32(len), 4)
|
|
||||||
|
|
||||||
g.constant_data << ConstantData{
|
|
||||||
offset: global.abs_address
|
|
||||||
data: buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast.IntegerLiteral {
|
|
||||||
mut buf := []u8{len: 8}
|
|
||||||
wtyp := g.get_wasm_type(global.ast_typ)
|
|
||||||
match wtyp {
|
|
||||||
type_i32 {
|
|
||||||
bin.little_endian_put_u32(mut buf, u32(global.init.val.int()))
|
|
||||||
}
|
|
||||||
type_i64 {
|
|
||||||
bin.little_endian_put_u64(mut buf, u64(global.init.val.i64()))
|
|
||||||
unsafe {
|
|
||||||
buf.len = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.constant_data << ConstantData{
|
|
||||||
offset: global.abs_address
|
|
||||||
data: buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
initialisers << global
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialisers
|
|
||||||
}
|
|
||||||
|
|
||||||
fn round_up_to_multiple(val int, multiple int) int {
|
|
||||||
return val + (multiple - val % multiple) % multiple
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) make_vinit() binaryen.Function {
|
|
||||||
runtime_inits := g.bake_constants_plus_initialisers()
|
|
||||||
|
|
||||||
g.bare_function_start()
|
|
||||||
|
|
||||||
mut body := runtime_inits.map(g.set_var_v(it.to_var(''), g.expr(it.init, it.ast_typ)))
|
|
||||||
|
|
||||||
for mod_name in g.table.modules {
|
|
||||||
if mod_name == 'v.reflection' {
|
|
||||||
g.w_error('the wasm backend does not implement `v.reflection` yet')
|
|
||||||
}
|
|
||||||
|
|
||||||
init_fn_name := if mod_name != 'builtin' { '${mod_name}.init' } else { 'init' }
|
|
||||||
if _ := g.table.find_fn(init_fn_name) {
|
|
||||||
body << binaryen.call(g.mod, init_fn_name.str, unsafe { nil }, 0, type_none)
|
|
||||||
}
|
|
||||||
cleanup_fn_name := if mod_name != 'builtin' { '${mod_name}.cleanup' } else { 'cleanup' }
|
|
||||||
if _ := g.table.find_fn(cleanup_fn_name) {
|
|
||||||
body << binaryen.call(g.mod, cleanup_fn_name.str, unsafe { nil }, 0, type_none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return g.bare_function('_vinit', g.mkblock(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) housekeeping() {
|
|
||||||
// `_vinit` should be used to initialise the WASM module,
|
|
||||||
// then `main.main` can be called safely.
|
|
||||||
vinit := g.make_vinit()
|
|
||||||
stack_base := round_up_to_multiple(g.constant_data_offset, 1024)
|
|
||||||
heap_base := if g.needs_stack {
|
|
||||||
stack_base + 1024 * 16 // 16KiB of stack
|
|
||||||
} else {
|
|
||||||
stack_base
|
|
||||||
}
|
|
||||||
pages_needed := heap_base / (1024 * 64) + 1
|
|
||||||
|
|
||||||
if g.needs_stack || g.constant_data.len != 0 {
|
|
||||||
data := g.constant_data.map(it.data.data)
|
|
||||||
data_len := g.constant_data.map(it.data.len)
|
|
||||||
data_offsets := g.constant_data.map(binaryen.constant(g.mod, binaryen.literalint32(it.offset)))
|
|
||||||
passive := []bool{len: g.constant_data.len, init: false}
|
|
||||||
|
|
||||||
binaryen.setmemory(g.mod, pages_needed, pages_needed + 4, c'memory', data.data,
|
|
||||||
passive.data, data_offsets.data, data_len.data, data.len, false, false, c'memory')
|
|
||||||
binaryen.addglobal(g.mod, c'__heap_base', type_i32, false, g.literalint(heap_base,
|
|
||||||
ast.int_type))
|
|
||||||
}
|
|
||||||
if g.needs_stack {
|
|
||||||
// `g.constant_data_offset` rounded up to a multiple of 1024
|
|
||||||
binaryen.addglobal(g.mod, c'__vsp', type_i32, true, g.literalint(stack_base, ast.int_type))
|
|
||||||
}
|
|
||||||
if g.pref.os == .wasi {
|
|
||||||
main_expr := g.mkblock([binaryen.call(g.mod, c'_vinit', unsafe { nil }, 0, type_none),
|
|
||||||
binaryen.call(g.mod, c'main.main', unsafe { nil }, 0, type_none)])
|
|
||||||
binaryen.addfunction(g.mod, c'_start', type_none, type_none, unsafe { nil }, 0,
|
|
||||||
main_expr)
|
|
||||||
binaryen.addfunctionexport(g.mod, c'_start', c'_start')
|
|
||||||
} else {
|
|
||||||
// In `browser` mode, and function can be exported and called regardless.
|
|
||||||
// To avoid uninitialised data, `_vinit` is set to be ran immediately on
|
|
||||||
// WASM module creation.
|
|
||||||
binaryen.setstart(g.mod, vinit)
|
|
||||||
}
|
|
||||||
}
|
|
12
vlib/v/gen/wasm/serialise/alignment_test.v
Normal file
12
vlib/v/gen/wasm/serialise/alignment_test.v
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import v.ast
|
||||||
|
import v.gen.wasm.serialise
|
||||||
|
|
||||||
|
fn test_alignment() {
|
||||||
|
table := ast.new_table()
|
||||||
|
|
||||||
|
mut pool := serialise.new_pool(table)
|
||||||
|
pool.append(ast.BoolLiteral{ val: true }, 0) // +0, +1
|
||||||
|
pool.append(ast.FloatLiteral{ val: '0' }, ast.f32_type) // +3, +4
|
||||||
|
pool.append(ast.BoolLiteral{ val: true }, 0) // +0, +1
|
||||||
|
assert pool.buf.len == 9
|
||||||
|
}
|
449
vlib/v/gen/wasm/serialise/serialise.v
Normal file
449
vlib/v/gen/wasm/serialise/serialise.v
Normal file
|
@ -0,0 +1,449 @@
|
||||||
|
// Copyright (c) 2023 l-m.dev. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
module serialise
|
||||||
|
|
||||||
|
import v.ast
|
||||||
|
// import v.eval
|
||||||
|
import math.bits
|
||||||
|
import strconv
|
||||||
|
|
||||||
|
[noinit]
|
||||||
|
pub struct Pool {
|
||||||
|
mut:
|
||||||
|
table &ast.Table
|
||||||
|
// eval eval.Eval
|
||||||
|
structs map[ast.Type]StructInfo
|
||||||
|
strings []StringInfo // string intern
|
||||||
|
pub:
|
||||||
|
null_terminated bool
|
||||||
|
intern_strings bool
|
||||||
|
store_relocs bool
|
||||||
|
pub mut:
|
||||||
|
buf []u8
|
||||||
|
relocs []Reloc
|
||||||
|
highest_alignment int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StringInfo {
|
||||||
|
pos int
|
||||||
|
len int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StructInfo {
|
||||||
|
pub mut:
|
||||||
|
offsets []int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Reloc {
|
||||||
|
pub:
|
||||||
|
pos int
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut p Pool) type_struct_info(typ ast.Type) ?StructInfo {
|
||||||
|
ts := p.table.sym(typ)
|
||||||
|
|
||||||
|
if ts.info !is ast.Struct {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ.idx() in p.structs {
|
||||||
|
return p.structs[typ.idx()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// will cache inside `p.structs`
|
||||||
|
p.type_size(typ)
|
||||||
|
return p.structs[typ.idx()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut p Pool) type_size(typ ast.Type) (int, int) {
|
||||||
|
ts := p.table.sym(typ)
|
||||||
|
if ts.size != -1 && typ.idx() in p.structs {
|
||||||
|
return ts.size, ts.align
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts.info is ast.Enum {
|
||||||
|
return p.table.type_size(ts.info.typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts.info !is ast.Struct {
|
||||||
|
return p.table.type_size(typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
ti := ts.info as ast.Struct
|
||||||
|
|
||||||
|
// code borrowed from native, inserted in wasm, and now here!
|
||||||
|
|
||||||
|
mut strc := StructInfo{}
|
||||||
|
mut size := 0
|
||||||
|
mut align := 1
|
||||||
|
for f in ti.fields {
|
||||||
|
f_size, f_align := p.type_size(f.typ)
|
||||||
|
if f_size == 0 {
|
||||||
|
strc.offsets << 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
padding := (f_align - size % f_align) % f_align
|
||||||
|
strc.offsets << size + padding
|
||||||
|
size += f_size + padding
|
||||||
|
if f_align > align {
|
||||||
|
align = f_align
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size = (size + align - 1) / align * align
|
||||||
|
p.structs[typ.idx()] = strc
|
||||||
|
|
||||||
|
mut ts_ := p.table.sym(typ)
|
||||||
|
ts_.size = size
|
||||||
|
ts_.align = align
|
||||||
|
|
||||||
|
return size, align
|
||||||
|
}
|
||||||
|
|
||||||
|
[params]
|
||||||
|
pub struct PoolOpts {
|
||||||
|
null_terminated bool = true
|
||||||
|
intern_strings bool = true
|
||||||
|
store_relocs bool = true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_pool(table &ast.Table, opts PoolOpts) Pool {
|
||||||
|
return Pool{
|
||||||
|
table: table
|
||||||
|
null_terminated: opts.null_terminated
|
||||||
|
intern_strings: opts.intern_strings
|
||||||
|
store_relocs: opts.store_relocs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) zero_fill(size int) {
|
||||||
|
// TODO: eventually support a way to utilise a BSS section
|
||||||
|
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
p.buf << 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) alignment(align int) int {
|
||||||
|
if align > p.highest_alignment {
|
||||||
|
p.highest_alignment = align
|
||||||
|
}
|
||||||
|
padding := (align - p.buf.len % align) % align
|
||||||
|
p.zero_fill(padding)
|
||||||
|
pos := p.buf.len
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn (mut p Pool) append_struct(init ast.StructInit) ?int {
|
||||||
|
old_len := p.buf.len
|
||||||
|
|
||||||
|
size, align := p.type_size(v.typ)
|
||||||
|
ts := g.table.sym(v.typ)
|
||||||
|
ts_info := ts.info as ast.Struct
|
||||||
|
|
||||||
|
pos := p.alignment(align)
|
||||||
|
|
||||||
|
if init.fields.len == 0 && !(ts_info.fields.any(it.has_default_expr)) {
|
||||||
|
for i := 0 ; i < size ; i++ {
|
||||||
|
p.buf << 0
|
||||||
|
}
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for i, f in ts_info.fields {
|
||||||
|
field_to_be_set := init.fields.map(it.name).filter(f.name)
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* for i, f in ts_info.fields {
|
||||||
|
field_to_be_set := init.fields.map(it.name).contains(f.name)
|
||||||
|
|
||||||
|
if !field_to_be_set {
|
||||||
|
offset := g.structs[v.typ.idx()].offsets[i]
|
||||||
|
offset_var := g.offset(v, f.typ, offset)
|
||||||
|
|
||||||
|
fsize, _ := g.get_type_size_align(f.typ)
|
||||||
|
|
||||||
|
if f.has_default_expr {
|
||||||
|
g.expr(f.default_expr, f.typ)
|
||||||
|
g.set(offset_var)
|
||||||
|
} else {
|
||||||
|
g.zero_fill(offset_var, fsize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for f in init.fields {
|
||||||
|
field := ts.find_field(f.name) or {
|
||||||
|
g.w_error('could not find field `${f.name}` on init')
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := g.structs[v.typ.idx()].offsets[field.i]
|
||||||
|
offset_var := g.offset(v, f.expected_type, offset)
|
||||||
|
|
||||||
|
g.expr(f.expr, f.expected_type)
|
||||||
|
g.set(offset_var)
|
||||||
|
} */
|
||||||
|
|
||||||
|
return pos
|
||||||
|
}*/
|
||||||
|
|
||||||
|
pub fn eval_escape_codes_raw(str string) !string {
|
||||||
|
mut buffer := []u8{}
|
||||||
|
|
||||||
|
mut i := 0
|
||||||
|
for i < str.len {
|
||||||
|
if str[i] != `\\` {
|
||||||
|
buffer << str[i]
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip \
|
||||||
|
i++
|
||||||
|
match str[i] {
|
||||||
|
`\\`, `'`, `"` {
|
||||||
|
buffer << str[i]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
`a`, `b`, `f` {
|
||||||
|
buffer << str[i] - u8(90)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
`n` {
|
||||||
|
buffer << `\n`
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
`r` {
|
||||||
|
buffer << `\r`
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
`t` {
|
||||||
|
buffer << `\t`
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
`u` {
|
||||||
|
i++
|
||||||
|
utf8 := strconv.parse_int(str[i..i + 4], 16, 16) or {
|
||||||
|
return error('invalid \\u escape code (${str[i..i + 4]})')
|
||||||
|
}
|
||||||
|
i += 4
|
||||||
|
buffer << u8(utf8)
|
||||||
|
buffer << u8(utf8 >> 8)
|
||||||
|
}
|
||||||
|
`v` {
|
||||||
|
buffer << `\v`
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
`x` {
|
||||||
|
i++
|
||||||
|
c := strconv.parse_int(str[i..i + 2], 16, 8) or {
|
||||||
|
return error('invalid \\x escape code (${str[i..i + 2]})')
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
buffer << u8(c)
|
||||||
|
}
|
||||||
|
`0`...`7` {
|
||||||
|
c := strconv.parse_int(str[i..i + 3], 8, 8) or {
|
||||||
|
return error('invalid escape code \\${str[i..i + 3]}')
|
||||||
|
}
|
||||||
|
i += 3
|
||||||
|
buffer << u8(c)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('invalid escape code \\${str[i]}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.bytestr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_escape_codes(str_lit ast.StringLiteral) !string {
|
||||||
|
if str_lit.is_raw {
|
||||||
|
return str_lit.val
|
||||||
|
}
|
||||||
|
|
||||||
|
return eval_escape_codes_raw(str_lit.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut p Pool) append_string(val string) int {
|
||||||
|
data := val.bytes()
|
||||||
|
|
||||||
|
if p.intern_strings {
|
||||||
|
for str in p.strings {
|
||||||
|
if data.len > str.len || (p.null_terminated && data.len != str.len) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: aggressive string interning if `p.null_terminated`
|
||||||
|
if p.buf[str.pos..str.pos + data.len] == data {
|
||||||
|
return str.pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := p.buf.len
|
||||||
|
p.buf << data
|
||||||
|
if p.null_terminated {
|
||||||
|
p.buf << 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p.strings << StringInfo{
|
||||||
|
pos: pos
|
||||||
|
len: data.len
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut p Pool) append(init ast.Expr, typ ast.Type) (int, bool) {
|
||||||
|
match init {
|
||||||
|
ast.BoolLiteral {
|
||||||
|
pos := p.buf.len
|
||||||
|
p.buf << u8(init.val)
|
||||||
|
return pos, true
|
||||||
|
}
|
||||||
|
ast.FloatLiteral {
|
||||||
|
assert typ.is_pure_float()
|
||||||
|
|
||||||
|
mut pos := 0
|
||||||
|
if typ == ast.f32_type {
|
||||||
|
pos = p.alignment(4)
|
||||||
|
p.u32(bits.f32_bits(init.val.f32()))
|
||||||
|
} else {
|
||||||
|
pos = p.alignment(8)
|
||||||
|
p.u64(bits.f64_bits(init.val.f64()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos, true
|
||||||
|
}
|
||||||
|
ast.IntegerLiteral {
|
||||||
|
assert typ.is_pure_int()
|
||||||
|
|
||||||
|
size, align := p.table.type_size(typ)
|
||||||
|
pos := p.alignment(align)
|
||||||
|
|
||||||
|
match size {
|
||||||
|
1 {
|
||||||
|
p.u8(u8(init.val.i8()))
|
||||||
|
}
|
||||||
|
2 {
|
||||||
|
p.u16(u16(init.val.i16()))
|
||||||
|
}
|
||||||
|
4 {
|
||||||
|
p.u32(u32(init.val.int()))
|
||||||
|
}
|
||||||
|
8 {
|
||||||
|
p.u64(u64(init.val.i64()))
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos, true
|
||||||
|
}
|
||||||
|
ast.CharLiteral {
|
||||||
|
// 3 extra bytes for improved program correctness, thank me later
|
||||||
|
rne := u32(eval_escape_codes_raw(init.val) or { panic('Pool.append: ${err}') }.runes()[0])
|
||||||
|
pos := p.alignment(4)
|
||||||
|
p.u32(rne)
|
||||||
|
|
||||||
|
return pos, true
|
||||||
|
}
|
||||||
|
ast.StringLiteral {
|
||||||
|
val := eval_escape_codes(init) or { panic('Pool.append: ${err}') }
|
||||||
|
str_pos := p.append_string(val)
|
||||||
|
|
||||||
|
if typ != ast.string_type {
|
||||||
|
// c'str'
|
||||||
|
return str_pos, true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, align := p.type_size(ast.string_type)
|
||||||
|
tss := p.table.sym(ast.string_type).info as ast.Struct
|
||||||
|
pos := p.alignment(align)
|
||||||
|
|
||||||
|
for field in tss.fields {
|
||||||
|
match field.name {
|
||||||
|
'str' {
|
||||||
|
p.ptr(str_pos)
|
||||||
|
}
|
||||||
|
'len' {
|
||||||
|
p.u32(u32(val.len))
|
||||||
|
}
|
||||||
|
'is_lit' {
|
||||||
|
p.u32(1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
panic('ast.string: field `${field.name}` is unknown')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos, true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
size, align := p.type_size(typ)
|
||||||
|
pos := p.alignment(align)
|
||||||
|
p.zero_fill(size)
|
||||||
|
return pos, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) u64(v u64) {
|
||||||
|
p.buf << u8(v)
|
||||||
|
p.buf << u8(v >> u64(8))
|
||||||
|
p.buf << u8(v >> u64(16))
|
||||||
|
p.buf << u8(v >> u64(24))
|
||||||
|
p.buf << u8(v >> u64(32))
|
||||||
|
p.buf << u8(v >> u64(40))
|
||||||
|
p.buf << u8(v >> u64(48))
|
||||||
|
p.buf << u8(v >> u64(56))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) u32(v u32) {
|
||||||
|
p.buf << u8(v)
|
||||||
|
p.buf << u8(v >> u32(8))
|
||||||
|
p.buf << u8(v >> u32(16))
|
||||||
|
p.buf << u8(v >> u32(24))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) u16(v u16) {
|
||||||
|
p.buf << u8(v)
|
||||||
|
p.buf << u8(v >> u32(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) u8(v u8) {
|
||||||
|
p.buf << v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Pool) ptr(offset int) int {
|
||||||
|
assert p.table.pointer_size in [1, 2, 4, 8]
|
||||||
|
pos := p.buf.len // p.alignment(p.table.pointer_size)
|
||||||
|
|
||||||
|
if p.store_relocs {
|
||||||
|
p.relocs << Reloc{
|
||||||
|
pos: pos
|
||||||
|
offset: offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match p.table.pointer_size {
|
||||||
|
1 {
|
||||||
|
p.u8(u8(offset))
|
||||||
|
}
|
||||||
|
2 {
|
||||||
|
p.u16(u16(offset))
|
||||||
|
}
|
||||||
|
4 {
|
||||||
|
p.u32(u32(offset))
|
||||||
|
}
|
||||||
|
8 {
|
||||||
|
p.u64(u64(offset))
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
return pos
|
||||||
|
}
|
|
@ -84,3 +84,60 @@ pub fn powi(a i64, b i64) i64 {
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sqrti(a i64) i64 {
|
||||||
|
mut x := a
|
||||||
|
mut q, mut r := i64(1), i64(0)
|
||||||
|
for ; q <= x; {
|
||||||
|
q <<= 2
|
||||||
|
}
|
||||||
|
for ; q > 1; {
|
||||||
|
q >>= 2
|
||||||
|
t := x - r - q
|
||||||
|
r >>= 1
|
||||||
|
if t >= 0 {
|
||||||
|
x = t
|
||||||
|
r += q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('--- test printing numbers')
|
||||||
|
println(-20)
|
||||||
|
println(0)
|
||||||
|
println(22)
|
||||||
|
println(-1000000022)
|
||||||
|
|
||||||
|
println('--- test powi')
|
||||||
|
println(powi(2, 62))
|
||||||
|
println(powi(0, -2))
|
||||||
|
println(powi(2, -1))
|
||||||
|
|
||||||
|
println('--- test sqrti')
|
||||||
|
println(sqrti(i64(123456789) * i64(123456789)))
|
||||||
|
println(sqrti(144))
|
||||||
|
println(sqrti(0))
|
||||||
|
|
||||||
|
println('--- test negate')
|
||||||
|
println(negate(20))
|
||||||
|
println(negate(-1))
|
||||||
|
|
||||||
|
println('--- test inc')
|
||||||
|
println(inc(20))
|
||||||
|
println(inc(-1))
|
||||||
|
|
||||||
|
println('--- test lcm')
|
||||||
|
println(lcm(2, 3))
|
||||||
|
println(lcm(-2, 3))
|
||||||
|
println(lcm(-2, -3))
|
||||||
|
println(lcm(0, 0))
|
||||||
|
|
||||||
|
println('--- test gcd')
|
||||||
|
|
||||||
|
println(gcd(6, 9))
|
||||||
|
println(gcd(6, -9))
|
||||||
|
println(gcd(-6, -9))
|
||||||
|
println(gcd(0, 0))
|
||||||
|
}
|
||||||
|
|
29
vlib/v/gen/wasm/tests/arith.vv.out
Normal file
29
vlib/v/gen/wasm/tests/arith.vv.out
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
--- test printing numbers
|
||||||
|
-20
|
||||||
|
0
|
||||||
|
22
|
||||||
|
-1000000022
|
||||||
|
--- test powi
|
||||||
|
4611686018427387904
|
||||||
|
-1
|
||||||
|
0
|
||||||
|
--- test sqrti
|
||||||
|
123456789
|
||||||
|
12
|
||||||
|
0
|
||||||
|
--- test negate
|
||||||
|
-20
|
||||||
|
1
|
||||||
|
--- test inc
|
||||||
|
20
|
||||||
|
-1
|
||||||
|
--- test lcm
|
||||||
|
6
|
||||||
|
6
|
||||||
|
6
|
||||||
|
0
|
||||||
|
--- test gcd
|
||||||
|
3
|
||||||
|
3
|
||||||
|
3
|
||||||
|
0
|
|
@ -3,12 +3,14 @@ struct TEST {
|
||||||
b i64
|
b i64
|
||||||
}
|
}
|
||||||
|
|
||||||
fn static_arrays() {
|
fn static_arrays() (int, int, i64) {
|
||||||
a := [8]int{}
|
a := [8]int{}
|
||||||
b := [10, 12, 150]!
|
b := [10, 12, 150]!
|
||||||
c := [TEST{}, TEST{
|
c := [TEST{}, TEST{
|
||||||
b: 10
|
b: 10
|
||||||
}]!
|
}]!
|
||||||
|
|
||||||
|
return a[2], b[1], c[1].b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_expression() {
|
fn index_expression() {
|
||||||
|
@ -16,7 +18,11 @@ fn index_expression() {
|
||||||
|
|
||||||
a := b[2]
|
a := b[2]
|
||||||
c := 'hello'[4]
|
c := 'hello'[4]
|
||||||
d := c'hello'[2]
|
d := unsafe { c'hello'[2] }
|
||||||
|
|
||||||
|
println(a)
|
||||||
|
println(c)
|
||||||
|
println(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_this(index int) int {
|
fn test_this(index int) int {
|
||||||
|
@ -31,9 +37,31 @@ struct AA {
|
||||||
a [10]&int
|
a [10]&int
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn test_stuff() &int {
|
||||||
a := AA{}
|
a := AA{}
|
||||||
|
|
||||||
mut b := &int(0)
|
mut b := &int(0)
|
||||||
b = a.a[2]
|
b = a.a[2]
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('--- static_arrays()')
|
||||||
|
a, b, c := static_arrays()
|
||||||
|
println(a)
|
||||||
|
println(b)
|
||||||
|
println(c)
|
||||||
|
|
||||||
|
println('--- index_expression()')
|
||||||
|
index_expression()
|
||||||
|
|
||||||
|
println('--- test_this()')
|
||||||
|
println(test_this(2))
|
||||||
|
println(test_this(10))
|
||||||
|
println(test_this(-1))
|
||||||
|
|
||||||
|
println('--- test_stuff()')
|
||||||
|
v := test_stuff()
|
||||||
|
println(v)
|
||||||
}
|
}
|
||||||
|
|
14
vlib/v/gen/wasm/tests/arrays.vv.out
Normal file
14
vlib/v/gen/wasm/tests/arrays.vv.out
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
--- static_arrays()
|
||||||
|
0
|
||||||
|
12
|
||||||
|
10
|
||||||
|
--- index_expression()
|
||||||
|
150
|
||||||
|
111
|
||||||
|
108
|
||||||
|
--- test_this()
|
||||||
|
108
|
||||||
|
10
|
||||||
|
10
|
||||||
|
--- test_stuff()
|
||||||
|
0
|
|
@ -1,7 +1,6 @@
|
||||||
fn test() {
|
fn test() {
|
||||||
print('hello!')
|
print('hello!')
|
||||||
println('hello!')
|
println('hello!')
|
||||||
panic('nooo!')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn str_methods() {
|
fn str_methods() {
|
||||||
|
@ -16,3 +15,19 @@ fn str_implicit() {
|
||||||
a := 100
|
a := 100
|
||||||
println(a + 10)
|
println(a + 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assertions() {
|
||||||
|
assert true, 'hello'
|
||||||
|
assert true
|
||||||
|
|
||||||
|
// assert false, 'no can do'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test()
|
||||||
|
str_methods()
|
||||||
|
str_implicit()
|
||||||
|
assertions()
|
||||||
|
|
||||||
|
// panic('nooo!')
|
||||||
|
}
|
||||||
|
|
6
vlib/v/gen/wasm/tests/builtin.vv.out
Normal file
6
vlib/v/gen/wasm/tests/builtin.vv.out
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
hello!hello!
|
||||||
|
128-192322
|
||||||
|
false
|
||||||
|
false
|
||||||
|
true
|
||||||
|
110
|
|
@ -59,15 +59,17 @@ fn addcfor() int {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
fn labelcfor() {
|
fn labelcfor() (int, int) {
|
||||||
|
mut idx := 0
|
||||||
mut val := 0
|
mut val := 0
|
||||||
|
|
||||||
hello: for {
|
hello: for {
|
||||||
for {
|
for {
|
||||||
|
val++
|
||||||
if val == 10 {
|
if val == 10 {
|
||||||
continue hello
|
continue hello
|
||||||
}
|
}
|
||||||
val++
|
idx++
|
||||||
|
|
||||||
if val == 100 {
|
if val == 100 {
|
||||||
break hello
|
break hello
|
||||||
|
@ -75,6 +77,8 @@ fn labelcfor() {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return val, idx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infcfor() int {
|
fn infcfor() int {
|
||||||
|
@ -89,3 +93,32 @@ fn infcfor() int {
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('--- func()')
|
||||||
|
println(func(10, true))
|
||||||
|
println(func(0, false))
|
||||||
|
println(func(0, true))
|
||||||
|
|
||||||
|
println('--- test()')
|
||||||
|
println(test(true))
|
||||||
|
println(test(false))
|
||||||
|
println(test(true && false))
|
||||||
|
|
||||||
|
println('--- boolfor()')
|
||||||
|
println(boolfor())
|
||||||
|
|
||||||
|
println('--- inffor()')
|
||||||
|
println(inffor())
|
||||||
|
|
||||||
|
println('--- addcfor()')
|
||||||
|
println(addcfor())
|
||||||
|
|
||||||
|
println('--- labelcfor()')
|
||||||
|
a, b := labelcfor()
|
||||||
|
println(a)
|
||||||
|
println(b)
|
||||||
|
|
||||||
|
println('--- infcfor()')
|
||||||
|
println(infcfor())
|
||||||
|
}
|
||||||
|
|
19
vlib/v/gen/wasm/tests/control_flow.vv.out
Normal file
19
vlib/v/gen/wasm/tests/control_flow.vv.out
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
--- func()
|
||||||
|
10
|
||||||
|
0
|
||||||
|
0
|
||||||
|
--- test()
|
||||||
|
2
|
||||||
|
5
|
||||||
|
5
|
||||||
|
--- boolfor()
|
||||||
|
1
|
||||||
|
--- inffor()
|
||||||
|
1
|
||||||
|
--- addcfor()
|
||||||
|
45
|
||||||
|
--- labelcfor()
|
||||||
|
100
|
||||||
|
99
|
||||||
|
--- infcfor()
|
||||||
|
10
|
|
@ -6,9 +6,10 @@ enum Hello as u64 {
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enums() {
|
fn enums() Hello {
|
||||||
mut a := Hello.a
|
mut a := Hello.a
|
||||||
a = .c
|
a = .c
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AA {
|
struct AA {
|
||||||
|
@ -19,6 +20,7 @@ struct AA {
|
||||||
fn of() {
|
fn of() {
|
||||||
a := __offsetof(AA, b)
|
a := __offsetof(AA, b)
|
||||||
b := sizeof(AA)
|
b := sizeof(AA)
|
||||||
|
_, _ := a, b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constant() int {
|
fn constant() int {
|
||||||
|
@ -55,3 +57,35 @@ fn ptr_arith() {
|
||||||
}
|
}
|
||||||
println((*b).str())
|
println((*b).str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn defer_if(cond bool) {
|
||||||
|
if cond {
|
||||||
|
defer {
|
||||||
|
println('defer_if: defer!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println('defer_if: start')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_defer() {
|
||||||
|
defer {
|
||||||
|
println('defer!')
|
||||||
|
}
|
||||||
|
println('before defer')
|
||||||
|
defer_if(true)
|
||||||
|
defer_if(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println('ptr_arith')
|
||||||
|
ptr_arith()
|
||||||
|
run_defer()
|
||||||
|
println('constants')
|
||||||
|
println(runtime_init)
|
||||||
|
println(hello)
|
||||||
|
// println(float)
|
||||||
|
println(integer)
|
||||||
|
println('enums')
|
||||||
|
println(int(enums()))
|
||||||
|
println(sizeof(Hello))
|
||||||
|
}
|
||||||
|
|
17
vlib/v/gen/wasm/tests/misc.vv.out
Normal file
17
vlib/v/gen/wasm/tests/misc.vv.out
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
ptr_arith
|
||||||
|
12
|
||||||
|
14
|
||||||
|
102
|
||||||
|
before defer
|
||||||
|
defer_if: start
|
||||||
|
defer_if: defer!
|
||||||
|
defer_if: start
|
||||||
|
defer!
|
||||||
|
constants
|
||||||
|
100
|
||||||
|
hello
|
||||||
|
|
||||||
|
888
|
||||||
|
enums
|
||||||
|
30
|
||||||
|
8
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user