mirror of https://github.com/vlang/v.git
Compare commits
37 Commits
2d838d5178
...
a9a94cfd51
Author | SHA1 | Date |
---|---|---|
Swastik Baranwal | a9a94cfd51 | |
yuyi | aef4367a27 | |
Felipe Pena | 413ffbfc3b | |
Felipe Pena | 029e8a815b | |
Turiiya | efcb15d05b | |
yuyi | 52a055b6bc | |
l-m | c422919481 | |
Swastik Baranwal | 1c7df29bed | |
Casper Kuethe | a43064af07 | |
yuyi | 045adb6600 | |
phoebe | 87dd5de191 | |
JalonSolov | 72cd9b80a3 | |
yuyi | 6b792b1257 | |
Felipe Pena | 6b29d628c3 | |
yuyi | 6a8a22891d | |
Delyan Angelov | acd581add5 | |
Delyan Angelov | 6b00685629 | |
Delyan Angelov | e7af25ec14 | |
Felipe Pena | f8e89ae91c | |
yuyi | c9e8dd56c2 | |
yuyi | 1728e4c73e | |
Wertzui123 | 921a2e1c2e | |
Swastik Baranwal | 0498f4c40f | |
Felipe Pena | 59eb76c81d | |
Felipe Pena | 8f3a1751e3 | |
Alexander Medvednikov | 5355c67ebe | |
Felipe Pena | 4f518c2850 | |
Delyan Angelov | 54635185c4 | |
yuyi | 17b576227f | |
kbkpbot | b3a6b73306 | |
yuyi | 97a726b188 | |
Felipe Pena | 7fe794a974 | |
encyclopaedia | e7e5a07aa2 | |
okk | 7d6e15fa66 | |
Delyan Angelov | ded6c38061 | |
Felipe Pena | de392003be | |
yuyi | 11f06e41c0 |
|
@ -85,18 +85,17 @@ jobs:
|
|||
# v run /tmp/gitly/tests/first_run.v
|
||||
# # /tmp/gitly/gitly -ci_run
|
||||
|
||||
# - name: Build V Language Server (VLS) vlang/vls
|
||||
# run: |
|
||||
# echo "Clone VLS"
|
||||
# git clone --depth 1 https://github.com/vlang/vls /tmp/vls
|
||||
# echo "Build VLS"
|
||||
# v /tmp/vls/cmd/vls
|
||||
# echo "Build VLS with -prod"
|
||||
# v -prod /tmp/vls/cmd/vls
|
||||
# echo "Build VLS with -gc boehm -skip-unused"
|
||||
# v -gc boehm -skip-unused /tmp/vls/cmd/vls
|
||||
# echo "Test VLS with gcc"
|
||||
# v -cc gcc test /tmp/vls
|
||||
- name: Build V Language Server (v-analyzer) v-analyzer/v-analyzer
|
||||
run: |
|
||||
echo "Clone v-analyzer"
|
||||
git clone --depth 1 https://github.com/v-analyzer/v-analyzer /tmp/v-analyzer
|
||||
cd /tmp/v-analyzer
|
||||
echo "Installing dependencies"
|
||||
v install
|
||||
echo "Build v-analyzer debug"
|
||||
v build.vsh debug
|
||||
echo "Build v-analyzer release"
|
||||
v build.vsh release
|
||||
|
||||
- name: Build vlang/go2v
|
||||
run: |
|
||||
|
|
|
@ -60,8 +60,10 @@ jobs:
|
|||
- name: Build V
|
||||
run: make -j4 && ./v symlink -githubci
|
||||
|
||||
- name: Install binaryen as build dependency for the V WASM backend
|
||||
run: ./v cmd/tools/install_binaryen.vsh
|
||||
- name: Install wasmer to execute WASM modules
|
||||
run: |
|
||||
curl https://get.wasmer.io -sSfL | sh
|
||||
sudo ln -s ~/.wasmer/bin/wasmer /usr/local/bin
|
||||
|
||||
- name: Build the V WASM backend
|
||||
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
||||
|
@ -82,8 +84,10 @@ jobs:
|
|||
- name: Build V
|
||||
run: make -j4 && ./v symlink -githubci
|
||||
|
||||
- name: Install binaryen as build dependency for the V WASM backend
|
||||
run: ./v cmd/tools/install_binaryen.vsh
|
||||
- name: Install wasmer to execute WASM modules
|
||||
run: |
|
||||
curl https://get.wasmer.io -sSfL | sh
|
||||
sudo ln -s ~/.wasmer/bin/wasmer /usr/local/bin
|
||||
|
||||
- name: Build the V WASM backend
|
||||
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
|
||||
|
@ -94,26 +98,26 @@ jobs:
|
|||
- name: Build examples
|
||||
run: VTEST_ONLY=wasm ./v build-examples
|
||||
|
||||
wasm-backend-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: 121
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build V
|
||||
run: .\make.bat -msvc
|
||||
- name: Symlink V
|
||||
run: .\v.exe symlink -githubci
|
||||
|
||||
- name: Install binaryen as build dependency for the V WASM backend
|
||||
run: v cmd/tools/install_binaryen.vsh
|
||||
|
||||
- name: Build the V WASM backend
|
||||
run: v -cc msvc -showcc -v cmd/tools/builders/wasm_builder.v
|
||||
|
||||
- name: Test the WASM backend
|
||||
run: v -stats test vlib/v/gen/wasm/tests/
|
||||
|
||||
- name: Build examples
|
||||
run: $env:VTEST_ONLY='wasm'; v build-examples
|
||||
# wasm-backend-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: 121
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
#
|
||||
# - name: Build V
|
||||
# run: .\make.bat -msvc
|
||||
# - name: Symlink V
|
||||
# run: .\v.exe symlink -githubci
|
||||
#
|
||||
# - name: Install binaryen as build dependency for the V WASM backend
|
||||
# run: v cmd/tools/install_binaryen.vsh
|
||||
#
|
||||
# - name: Build the V WASM backend
|
||||
# run: v -cc msvc -showcc -v cmd/tools/builders/wasm_builder.v
|
||||
#
|
||||
# - name: Test the WASM backend
|
||||
# run: v -stats test vlib/v/gen/wasm/tests/
|
||||
#
|
||||
# - name: Build examples
|
||||
# run: $env:VTEST_ONLY='wasm'; v build-examples
|
||||
|
|
|
@ -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',
|
||||
'os.bare',
|
||||
'os2',
|
||||
'picohttpparser',
|
||||
'picoev',
|
||||
'szip',
|
||||
'v.eval',
|
||||
]
|
||||
|
|
|
@ -625,9 +625,8 @@ pub fn prepare_test_session(zargs string, folder string, oskipped []string, main
|
|||
continue
|
||||
}
|
||||
$if windows {
|
||||
// skip pico and process/command examples on windows
|
||||
if fnormalised.ends_with('examples/pico/pico.v')
|
||||
|| fnormalised.ends_with('examples/process/command.v') {
|
||||
// skip process/command examples on windows
|
||||
if fnormalised.ends_with('examples/process/command.v') {
|
||||
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..])
|
||||
}
|
||||
.wasm {
|
||||
assert_wasm_backend_thirdparty()
|
||||
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
|
||||
import strconv
|
||||
|
||||
fn test_atoi() ? {
|
||||
assert strconv.atoi('1')? == 1
|
||||
assert strconv.atoi('one')? == 1 // test will fail
|
||||
fn test_atoi() ! {
|
||||
assert strconv.atoi('1')! == 1
|
||||
assert strconv.atoi('one')! == 1 // test will fail
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -6664,7 +6664,7 @@ println('c: ${c}') // 120
|
|||
```
|
||||
|
||||
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
|
||||
|
||||
|
@ -6738,7 +6738,7 @@ fn sh(cmd string) {
|
|||
rmdir_all('build') or {}
|
||||
|
||||
// Create build/, never fails as build/ does not exist
|
||||
mkdir('build')?
|
||||
mkdir('build')!
|
||||
|
||||
// Move *.v files to build/
|
||||
result := execute('mv *.v build/')
|
||||
|
@ -6749,7 +6749,7 @@ if result.exit_code != 0 {
|
|||
sh('ls')
|
||||
|
||||
// Similar to:
|
||||
// files := ls('.')?
|
||||
// files := ls('.')!
|
||||
// mut count := 0
|
||||
// if files.len > 0 {
|
||||
// for file in files {
|
||||
|
|
|
@ -3,7 +3,7 @@ module some_module
|
|||
import eventbus
|
||||
|
||||
const (
|
||||
eb = eventbus.new()
|
||||
eb = eventbus.new[string]()
|
||||
)
|
||||
|
||||
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.'})
|
||||
}
|
||||
|
||||
pub fn get_subscriber() eventbus.Subscriber {
|
||||
pub fn get_subscriber() eventbus.Subscriber[string] {
|
||||
return *some_module.eb.subscriber
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
const (
|
||||
port = 8088
|
||||
port = 8089
|
||||
)
|
||||
|
||||
struct Message {
|
||||
|
@ -24,21 +24,25 @@ fn hello_response() string {
|
|||
}
|
||||
|
||||
fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Response) {
|
||||
if picohttpparser.cmpn(req.method, 'GET ', 4) {
|
||||
if picohttpparser.cmp(req.path, '/t') {
|
||||
if req.method == 'GET' {
|
||||
if req.path == '/t' {
|
||||
res.http_ok()
|
||||
res.header_server()
|
||||
res.header_date()
|
||||
res.plain()
|
||||
res.body(hello_response())
|
||||
} else if picohttpparser.cmp(req.path, '/j') {
|
||||
} else if req.path == '/j' {
|
||||
res.http_ok()
|
||||
res.header_server()
|
||||
res.header_date()
|
||||
res.json()
|
||||
res.body(json_response())
|
||||
} else {
|
||||
res.http_404()
|
||||
res.http_ok()
|
||||
res.header_server()
|
||||
res.header_date()
|
||||
res.html()
|
||||
res.body('Hello Picoev!\n')
|
||||
}
|
||||
} else {
|
||||
res.http_405()
|
||||
|
@ -48,5 +52,6 @@ fn callback(data voidptr, req picohttpparser.Request, mut res picohttpparser.Res
|
|||
|
||||
fn main() {
|
||||
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
|
||||
|
||||
import strings
|
||||
|
||||
// Common arrays functions:
|
||||
// - min / max - return the value of the 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) }
|
||||
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}
|
||||
// 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.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
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ const cp_utf8 = 65001
|
|||
// 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.
|
||||
// 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 {
|
||||
$if windows {
|
||||
unsafe {
|
||||
|
@ -29,19 +31,25 @@ pub fn (_str string) to_wide() &u16 {
|
|||
for i, r in srunes {
|
||||
result[i] = u16(r)
|
||||
}
|
||||
result[srunes.len] = 0
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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]
|
||||
pub fn string_from_wide(_wstr &u16) string {
|
||||
$if windows {
|
||||
unsafe {
|
||||
wstr_len := C.wcslen(_wstr)
|
||||
return string_from_wide2(_wstr, wstr_len)
|
||||
return string_from_wide2(_wstr, int(wstr_len))
|
||||
}
|
||||
} $else {
|
||||
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
|
||||
// string_from_wide, but it requires you to know the input string length,
|
||||
// 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]
|
||||
pub fn string_from_wide2(_wstr &u16, len int) string {
|
||||
$if windows {
|
||||
|
|
|
@ -72,6 +72,7 @@ fn test_string_from_wide2() {
|
|||
|
||||
fn test_reverse_cyrillic_with_string_from_wide() {
|
||||
s := 'Проба'
|
||||
z := unsafe { string_from_wide(s.to_wide()) }
|
||||
ws := s.to_wide()
|
||||
z := unsafe { string_from_wide(ws) }
|
||||
assert z == s
|
||||
}
|
||||
|
|
|
@ -6,6 +6,20 @@ fn __memory_grow(size usize) usize
|
|||
fn __memory_fill(dest &u8, value isize, 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 returns a `byteptr` pointing to the memory address of the allocated space.
|
||||
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
|
||||
|
|
|
@ -7,7 +7,7 @@ pub fn print(s string) {
|
|||
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.
|
||||
|
@ -20,7 +20,7 @@ pub fn println(s string) {
|
|||
len: 1
|
||||
}]!
|
||||
|
||||
WASM.fd_write(1, &elm[0], 2, -1)
|
||||
WASM.fd_write(1, &elm[0], 2, 0)
|
||||
}
|
||||
|
||||
// eprint prints a message to stderr.
|
||||
|
@ -30,7 +30,7 @@ pub fn eprint(s string) {
|
|||
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.
|
||||
|
@ -43,7 +43,7 @@ pub fn eprintln(s string) {
|
|||
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.
|
||||
|
@ -57,6 +57,5 @@ pub fn exit(code int) {
|
|||
pub fn panic(s string) {
|
||||
eprint('V panic: ')
|
||||
eprintln(s)
|
||||
_ := *&u8(-1)
|
||||
exit(1)
|
||||
}
|
||||
|
|
|
@ -52,21 +52,18 @@ fn (nn int) str_l(max int) string {
|
|||
}
|
||||
diff := max - index
|
||||
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(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`.
|
||||
// Example: assert i8(-2).str() == '-2'
|
||||
pub fn (n i8) str() string {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
||||
module builtin
|
||||
|
||||
struct CIOVec {
|
||||
|
@ -9,6 +8,9 @@ struct CIOVec {
|
|||
type Errno = u16
|
||||
type FileDesc = int
|
||||
|
||||
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
||||
fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno
|
||||
|
||||
[wasm_import_namespace: 'wasi_snapshot_preview1']
|
||||
[noreturn]
|
||||
fn WASM.proc_exit(rval int)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
1. `new()` - create a new `EventBus`
|
||||
1. `new[T]()` - create a new `EventBus`
|
||||
2. `EventBus.new[T]()` - create a new `EventBus`
|
||||
|
||||
### Structs:
|
||||
|
||||
**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
|
||||
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:**
|
||||
|
||||
1. `subscribe(name string, handler EventHandlerFn)` - subscribe to an event
|
||||
2. `subscribe_once(name string, handler EventHandlerFn)` - subscribe only once to an event
|
||||
3. `subscribe_method(name string, handler EventHandlerFn, receiver voidptr)` - subscribe to
|
||||
1. `subscribe(name T, handler EventHandlerFn)` - subscribe to an event
|
||||
2. `subscribe_once(name T, handler EventHandlerFn)` - subscribe only once to an event
|
||||
3. `subscribe_method(name T, handler EventHandlerFn, receiver voidptr)` - subscribe to
|
||||
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.
|
||||
4. `is_subscribed(name string)` - check if we are subscribed to an event
|
||||
5. `unsubscribe(name string)` - unsubscribe from an event
|
||||
4. `is_subscribed(name T)` - check if we are subscribed to an event
|
||||
5. `unsubscribe(name T)` - unsubscribe from an event
|
||||
|
||||
**Event Handler Signature:**
|
||||
|
||||
|
@ -63,7 +64,7 @@ import eventbus
|
|||
|
||||
// initialize it globally
|
||||
const (
|
||||
eb = eventbus.new()
|
||||
eb = eventbus.new[string]()
|
||||
)
|
||||
|
||||
fn main() {
|
||||
|
@ -88,7 +89,7 @@ module main
|
|||
|
||||
import eventbus
|
||||
|
||||
const eb = eventbus.new()
|
||||
const eb = eventbus.new[string]()
|
||||
|
||||
struct Work {
|
||||
hours int
|
||||
|
|
|
@ -2,59 +2,70 @@ module eventbus
|
|||
|
||||
pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr)
|
||||
|
||||
pub struct Publisher {
|
||||
pub struct Publisher[T] {
|
||||
mut:
|
||||
registry &Registry = unsafe { nil }
|
||||
registry &Registry[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
pub struct Subscriber {
|
||||
pub struct Subscriber[T] {
|
||||
mut:
|
||||
registry &Registry = unsafe { nil }
|
||||
registry &Registry[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
struct Registry {
|
||||
struct Registry[T] {
|
||||
mut:
|
||||
events []EventHandler
|
||||
events []EventHandler[T]
|
||||
}
|
||||
|
||||
struct EventHandler {
|
||||
name string
|
||||
struct EventHandler[T] {
|
||||
name T
|
||||
handler EventHandlerFn
|
||||
receiver voidptr = unsafe { nil }
|
||||
once bool
|
||||
}
|
||||
|
||||
pub struct EventBus {
|
||||
pub struct EventBus[T] {
|
||||
pub mut:
|
||||
registry &Registry = unsafe { nil }
|
||||
publisher &Publisher = unsafe { nil }
|
||||
subscriber &Subscriber = unsafe { nil }
|
||||
registry &Registry[T] = unsafe { nil }
|
||||
publisher &Publisher[T] = unsafe { nil }
|
||||
subscriber &Subscriber[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
pub fn new() &EventBus {
|
||||
registry := &Registry{
|
||||
// EventBus.new[T] create a new eventbus with event type T.
|
||||
pub fn EventBus.new[T]() &EventBus[T] {
|
||||
registry := &Registry[T]{
|
||||
events: []
|
||||
}
|
||||
return &EventBus{registry, &Publisher{registry}, &Subscriber{registry}}
|
||||
return &EventBus[T]{registry, &Publisher[T]{registry}, &Subscriber[T]{registry}}
|
||||
}
|
||||
|
||||
// EventBus Methods
|
||||
pub fn (eb &EventBus) publish(name string, sender voidptr, args voidptr) {
|
||||
// new[T] create a new eventbus with event type T.
|
||||
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
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
// Publisher Methods
|
||||
fn (mut pb Publisher) publish(name string, sender voidptr, args voidptr) {
|
||||
// publish publish an event with provided Params & name.
|
||||
fn (mut pb Publisher[T]) publish(name T, sender voidptr, args voidptr) {
|
||||
for event in pb.registry.events {
|
||||
if event.name == name {
|
||||
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))
|
||||
}
|
||||
|
||||
fn (mut p Publisher) clear_all() {
|
||||
// clear_all clear all subscribers.
|
||||
fn (mut p Publisher[T]) clear_all() {
|
||||
p.registry.events.clear()
|
||||
}
|
||||
|
||||
// Subscriber Methods
|
||||
pub fn (mut s Subscriber) subscribe(name string, handler EventHandlerFn) {
|
||||
s.registry.events << EventHandler{
|
||||
// subscribe subscribe to an event `name`.
|
||||
pub fn (mut s Subscriber[T]) subscribe(name T, handler EventHandlerFn) {
|
||||
s.registry.events << EventHandler[T]{
|
||||
name: name
|
||||
handler: handler
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut s Subscriber) subscribe_method(name string, handler EventHandlerFn, receiver voidptr) {
|
||||
s.registry.events << EventHandler{
|
||||
// subscribe_method subscribe to an event `name` and also set the `receiver` as a parameter.
|
||||
pub fn (mut s Subscriber[T]) subscribe_method(name T, handler EventHandlerFn, receiver voidptr) {
|
||||
s.registry.events << EventHandler[T]{
|
||||
name: name
|
||||
handler: handler
|
||||
receiver: receiver
|
||||
}
|
||||
}
|
||||
|
||||
// unsubscribe_method unsubscribe a receiver for only one method
|
||||
pub fn (mut s Subscriber) unsubscribe_method(name string, receiver voidptr) {
|
||||
// unsubscribe_method unsubscribe a receiver for only one method.
|
||||
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))
|
||||
}
|
||||
|
||||
// unsubscribe_receiver unsubscribes a receiver from all events
|
||||
pub fn (mut s Subscriber) unsubscribe_receiver(receiver voidptr) {
|
||||
// unsubscribe_receiver unsubscribes a receiver from all events.
|
||||
pub fn (mut s Subscriber[T]) unsubscribe_receiver(receiver voidptr) {
|
||||
s.registry.events = s.registry.events.filter(it.receiver != receiver)
|
||||
}
|
||||
|
||||
pub fn (mut s Subscriber) subscribe_once(name string, handler EventHandlerFn) {
|
||||
s.registry.events << EventHandler{
|
||||
// subscribe_once subscribe only once to an event `name`.
|
||||
pub fn (mut s Subscriber[T]) subscribe_once(name T, handler EventHandlerFn) {
|
||||
s.registry.events << EventHandler[T]{
|
||||
name: name
|
||||
handler: handler
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// is_subscribed_method checks whether a receiver was already subscribed for any events.
|
||||
pub fn (s &Subscriber[T]) is_subscribed_method(name T, receiver voidptr) bool {
|
||||
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)
|
||||
s.registry.events = s.registry.events.filter(!(it.name == name && it.handler == handler))
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ struct FakeReceiver {
|
|||
ok bool
|
||||
}
|
||||
|
||||
fn test_eventbus() {
|
||||
fn test_eventbus_string() {
|
||||
ev_data := &EventData{'hello'}
|
||||
mut eb := eventbus.new()
|
||||
mut eb := eventbus.new[string]()
|
||||
eb.subscriber.subscribe_once('on_test', on_test)
|
||||
assert eb.has_subscriber('on_test')
|
||||
assert !eb.has_subscriber('not_exist')
|
||||
assert eb.subscriber.is_subscribed('on_test')
|
||||
eb.publish('on_test', eb, ev_data)
|
||||
assert !eb.has_subscriber('on_test')
|
||||
|
@ -25,10 +26,52 @@ fn test_eventbus() {
|
|||
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() {
|
||||
// Does not really test subscribe_method idinvidually though
|
||||
// given
|
||||
mut eb := eventbus.new()
|
||||
mut eb := eventbus.new[string]()
|
||||
r := FakeReceiver{}
|
||||
|
||||
assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
||||
|
@ -41,7 +84,7 @@ fn test_subscribe_method() {
|
|||
|
||||
fn test_unsubscribe_method() {
|
||||
// given
|
||||
mut eb := eventbus.new()
|
||||
mut eb := eventbus.new[string]()
|
||||
r := FakeReceiver{}
|
||||
r2 := FakeReceiver{}
|
||||
|
||||
|
@ -58,7 +101,7 @@ fn test_unsubscribe_method() {
|
|||
fn test_publish() {
|
||||
// given
|
||||
ev_data := &EventData{'hello'}
|
||||
mut eb := eventbus.new()
|
||||
mut eb := eventbus.new[string]()
|
||||
|
||||
// when
|
||||
eb.subscriber.subscribe_once('on_test', on_test)
|
||||
|
@ -71,7 +114,7 @@ fn test_publish() {
|
|||
|
||||
fn test_publish_with_receiver() {
|
||||
// given
|
||||
mut eb := eventbus.new()
|
||||
mut eb := eventbus.new[string]()
|
||||
ev_data := &EventData{'hello'}
|
||||
r := FakeReceiver{}
|
||||
|
||||
|
@ -85,7 +128,7 @@ fn test_publish_with_receiver() {
|
|||
|
||||
fn test_unsubscribe_reveiver() {
|
||||
// given
|
||||
mut eb := eventbus.new()
|
||||
mut eb := eventbus.new[string]()
|
||||
r := &FakeReceiver{}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// end finishes drawing for the context.
|
||||
pub fn (ctx &Context) end() {
|
||||
pub enum EndEnum {
|
||||
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 ? {
|
||||
ctx.show_fps()
|
||||
} $else {
|
||||
|
@ -539,7 +587,14 @@ pub fn (ctx &Context) end() {
|
|||
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()
|
||||
gfx.end_pass()
|
||||
gfx.commit()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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{}
|
||||
sdir := list_dir.bytestr()
|
||||
for lfile in sdir.split('\n') {
|
||||
if lfile.len > 56 {
|
||||
dir << lfile#[56..lfile.len - 1]
|
||||
continue
|
||||
}
|
||||
if lfile.len > 1 {
|
||||
dir << lfile.after(' ').trim_space()
|
||||
trimmed := lfile.after(':')
|
||||
dir << trimmed#[3..trimmed.len - 1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
return dir
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## 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".
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
import net
|
||||
import picohttpparser
|
||||
import time
|
||||
|
||||
#include <errno.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <signal.h>
|
||||
#flag -I @VEXEROOT/thirdparty/picoev
|
||||
#flag @VEXEROOT/thirdparty/picoev/picoev.o
|
||||
#include "src/picoev.h"
|
||||
pub const (
|
||||
max_fds = 1024
|
||||
max_queue = 4096
|
||||
|
||||
[typedef]
|
||||
struct C.picoev_loop {}
|
||||
|
||||
fn C.picoev_del(&C.picoev_loop, int) int
|
||||
|
||||
fn C.picoev_set_timeout(&C.picoev_loop, int, int)
|
||||
|
||||
// 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
|
||||
// events
|
||||
picoev_read = 1
|
||||
picoev_write = 2
|
||||
picoev_timeout = 4
|
||||
picoev_add = 0x40000000
|
||||
picoev_del = 0x20000000
|
||||
picoev_readwrite = 3 // 1 xor 2
|
||||
)
|
||||
|
||||
enum Event {
|
||||
read = C.PICOEV_READ
|
||||
write = C.PICOEV_WRITE
|
||||
timeout = C.PICOEV_TIMEOUT
|
||||
add = C.PICOEV_ADD
|
||||
del = C.PICOEV_DEL
|
||||
readwrite = C.PICOEV_READWRITE
|
||||
// Target is a data representation of everything that needs to be associated with a single
|
||||
// file descriptor (connection)
|
||||
pub struct Target {
|
||||
pub mut:
|
||||
fd int
|
||||
loop_id int = -1
|
||||
events u32
|
||||
cb fn (int, int, voidptr)
|
||||
// used internally by the kqueue implementation
|
||||
backend int
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
|
@ -62,137 +40,230 @@ pub:
|
|||
max_write int = 8192
|
||||
}
|
||||
|
||||
struct Picoev {
|
||||
loop &C.picoev_loop = unsafe { nil }
|
||||
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
|
||||
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError)
|
||||
user_data voidptr
|
||||
[heap]
|
||||
pub struct Picoev {
|
||||
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
|
||||
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
|
||||
user_data voidptr = unsafe { nil }
|
||||
|
||||
timeout_secs int
|
||||
max_headers int
|
||||
max_read int
|
||||
max_write int
|
||||
max_headers int = 100
|
||||
max_read int = 4096
|
||||
max_write int = 8192
|
||||
mut:
|
||||
date &u8 = unsafe { nil }
|
||||
buf &u8 = unsafe { nil }
|
||||
idx [1024]int
|
||||
out &u8 = unsafe { nil }
|
||||
loop &LoopType = unsafe { nil }
|
||||
file_descriptors [max_fds]&Target
|
||||
timeouts map[int]i64
|
||||
num_loops int
|
||||
|
||||
buf &u8 = unsafe { nil }
|
||||
idx [1024]int
|
||||
out &u8 = unsafe { nil }
|
||||
|
||||
date string
|
||||
}
|
||||
|
||||
[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')
|
||||
// init fills the `file_descriptors` array
|
||||
pub fn (mut pv Picoev) init() {
|
||||
assert picoev.max_fds > 0
|
||||
|
||||
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')
|
||||
}
|
||||
} $else {
|
||||
if C.fcntl(fd, C.F_SETFL, C.O_NONBLOCK) != 0 {
|
||||
return error('fcntl failed')
|
||||
}
|
||||
|
||||
// add adds a file descriptor to the loop
|
||||
[direct_array_access]
|
||||
pub fn (mut pv Picoev) add(fd int, events int, timeout int, cb voidptr) int {
|
||||
assert fd < picoev.max_fds
|
||||
|
||||
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]
|
||||
fn close_conn(loop &C.picoev_loop, fd int) {
|
||||
C.picoev_del(voidptr(loop), fd)
|
||||
C.close(fd)
|
||||
}
|
||||
|
||||
[inline]
|
||||
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)
|
||||
// accept_callback accepts a new connection from `listen_fd` and adds it to the loop
|
||||
fn accept_callback(listen_fd int, events int, cb_arg voidptr) {
|
||||
mut pv := unsafe { &Picoev(cb_arg) }
|
||||
newfd := accept(listen_fd)
|
||||
if newfd >= picoev.max_fds {
|
||||
// should never happen
|
||||
close_socket(newfd)
|
||||
return
|
||||
} else if (events & int(Event.read)) != 0 {
|
||||
C.picoev_set_timeout(voidptr(loop), fd, p.timeout_secs)
|
||||
}
|
||||
|
||||
// Request init
|
||||
mut buf := p.buf
|
||||
$if trace_fd ? {
|
||||
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 {
|
||||
buf += fd * p.max_read // pointer magic
|
||||
buf += fd * pv.max_read // pointer magic
|
||||
}
|
||||
mut req := picohttpparser.Request{}
|
||||
|
||||
// Response init
|
||||
mut out := p.out
|
||||
mut out := pv.out
|
||||
unsafe {
|
||||
out += fd * p.max_write // pointer magic
|
||||
out += fd * pv.max_write // pointer magic
|
||||
}
|
||||
mut res := picohttpparser.Response{
|
||||
fd: fd
|
||||
date: p.date
|
||||
buf_start: out
|
||||
buf: out
|
||||
date: pv.date.str
|
||||
}
|
||||
|
||||
for {
|
||||
// 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 {
|
||||
// connection closed by peer
|
||||
close_conn(loop, fd)
|
||||
pv.close_conn(fd)
|
||||
return
|
||||
} else if r == -1 {
|
||||
// error
|
||||
if C.errno == C.EAGAIN {
|
||||
// try again later
|
||||
return
|
||||
}
|
||||
if C.errno == C.EWOULDBLOCK {
|
||||
// try again later
|
||||
if fatal_socket_error(fd) == false {
|
||||
return
|
||||
}
|
||||
|
||||
// fatal error
|
||||
close_conn(loop, fd)
|
||||
pv.close_conn(fd)
|
||||
return
|
||||
}
|
||||
p.idx[fd] += r
|
||||
pv.idx[fd] += r
|
||||
|
||||
mut s := unsafe { tos(buf, p.idx[fd]) }
|
||||
pret := req.parse_request(s, p.max_headers) // Parse request via picohttpparser
|
||||
mut s := unsafe { tos(buf, pv.idx[fd]) }
|
||||
pret := req.parse_request(s) or {
|
||||
// Parse error
|
||||
pv.err_cb(pv.user_data, req, mut &res, err)
|
||||
return
|
||||
}
|
||||
if pret > 0 { // Success
|
||||
break
|
||||
} else if pret == -1 { // Parse error
|
||||
p.err_cb(p.user_data, req, mut &res, error('ParseError'))
|
||||
return
|
||||
}
|
||||
|
||||
assert pret == -2
|
||||
// request is incomplete, continue the loop
|
||||
if p.idx[fd] == sizeof(buf) {
|
||||
p.err_cb(p.user_data, req, mut &res, error('RequestIsTooLongError'))
|
||||
if pv.idx[fd] == sizeof(buf) {
|
||||
pv.err_cb(pv.user_data, req, mut &res, error('RequestIsTooLongError'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Callback (should call .end() itself)
|
||||
p.cb(p.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)
|
||||
pv.cb(pv.user_data, req, mut &res)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,42 +272,12 @@ fn default_err_cb(data voidptr, req picohttpparser.Request, mut res picohttppars
|
|||
res.end()
|
||||
}
|
||||
|
||||
// new creates a `Picoev` struct and initializes the main loop
|
||||
pub fn new(config Config) &Picoev {
|
||||
fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0)
|
||||
assert fd != -1
|
||||
listen_fd := listen(config)
|
||||
|
||||
// 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{
|
||||
loop: loop
|
||||
num_loops: 1
|
||||
cb: config.cb
|
||||
err_cb: config.err_cb
|
||||
user_data: config.user_data
|
||||
|
@ -244,25 +285,45 @@ pub fn new(config Config) &Picoev {
|
|||
max_headers: config.max_headers
|
||||
max_read: config.max_read
|
||||
max_write: config.max_write
|
||||
date: &u8(C.get_date())
|
||||
buf: unsafe { malloc_noscan(picoev.max_fds * config.max_read + 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)
|
||||
spawn update_date(mut pv)
|
||||
// epoll for linux
|
||||
// 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
|
||||
}
|
||||
|
||||
pub fn (p Picoev) serve() {
|
||||
// serve starts the Picoev server
|
||||
pub fn (mut pv Picoev) serve() {
|
||||
spawn update_date(mut pv)
|
||||
|
||||
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 {
|
||||
p.date = &u8(C.get_date())
|
||||
C.usleep(1000000)
|
||||
// get GMT (UTC) time for the HTTP Date header
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
||||
`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."
|
||||
|
|
|
@ -1,20 +1,76 @@
|
|||
module picohttpparser
|
||||
|
||||
[inline; unsafe]
|
||||
fn cpy(dst &u8, src &u8, len int) int {
|
||||
unsafe { C.memcpy(dst, src, len) }
|
||||
return len
|
||||
}
|
||||
const (
|
||||
// vfmt off
|
||||
g_digits_lut = [
|
||||
`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]
|
||||
pub fn cmp(dst string, src string) bool {
|
||||
if dst.len != src.len {
|
||||
return false
|
||||
// u64toa converts `value` to an ascii string and stores it at `buf_start`
|
||||
// then it returns the length of the ascii string (branch lookup table implementation)
|
||||
[direct_array_access; inline]
|
||||
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]
|
||||
pub fn cmpn(dst string, src string, n int) bool {
|
||||
return unsafe { C.memcmp(dst.str, src.str, n) == 0 }
|
||||
v := u32(value)
|
||||
if v < 10_000 {
|
||||
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
|
||||
|
||||
#flag -I @VEXEROOT/thirdparty/picohttpparser
|
||||
#flag @VEXEROOT/thirdparty/picohttpparser/picohttpparser.o
|
||||
// NOTE: picohttpparser is designed for speed. Please do some benchmarks when
|
||||
// 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 {
|
||||
pub:
|
||||
name &char
|
||||
name_len int
|
||||
value &char
|
||||
value_len int
|
||||
fn (mut r Request) phr_parse_request_path(buf_start &u8, buf_end &u8, mut pret Pret) {
|
||||
mut buf := unsafe { buf_start + 0 }
|
||||
|
||||
// ADVANCE_TOKEN
|
||||
method := advance_token(buf, buf_end, mut pret)
|
||||
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
|
||||
fn C.phr_parse_request_path_pipeline(buf_start &char, len usize, method PPchar, method_len &usize, path PPchar, path_len &usize) int
|
||||
fn C.get_date() &char
|
||||
// parse request line
|
||||
r.phr_parse_request_path(buf, buf_end, mut pret)
|
||||
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) {
|
||||
fn C.u64toa(buffer &char, value u64) int
|
||||
return r.parse_headers(buf, buf_end, mut pret)
|
||||
}
|
||||
|
||||
[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
|
||||
|
||||
const (
|
||||
max_headers = 100
|
||||
)
|
||||
|
||||
pub struct Header {
|
||||
pub mut:
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
pub struct Request {
|
||||
mut:
|
||||
prev_len int
|
||||
pub mut:
|
||||
method string
|
||||
path string
|
||||
headers [100]C.phr_header
|
||||
num_headers u64
|
||||
headers [max_headers]Header
|
||||
num_headers int
|
||||
body string
|
||||
}
|
||||
|
||||
[inline]
|
||||
pub fn (mut r Request) parse_request(s string, max_headers int) int {
|
||||
method_len := usize(0)
|
||||
path_len := usize(0)
|
||||
minor_version := 0
|
||||
num_headers := usize(max_headers)
|
||||
// Pret contains the nr of bytes read, a negative number indicates an error
|
||||
struct Pret {
|
||||
pub mut:
|
||||
err string
|
||||
// -1 indicates a parse error and -2 means the request is parsed
|
||||
ret int
|
||||
}
|
||||
|
||||
pret := C.phr_parse_request(&char(s.str), s.len, voidptr(&r.method.str), &method_len,
|
||||
voidptr(&r.path.str), &path_len, &minor_version, &r.headers[0], &num_headers,
|
||||
r.prev_len)
|
||||
if pret > 0 {
|
||||
unsafe {
|
||||
r.method = tos(r.method.str, int(method_len))
|
||||
r.path = tos(r.path.str, int(path_len))
|
||||
// parse_request parses a raw HTTP request and returns the number of bytes read.
|
||||
// -1 indicates a parse error and -2 means the request is parsed
|
||||
[inline]
|
||||
pub fn (mut r Request) parse_request(s string) !int {
|
||||
mut buf := s.str
|
||||
buf_end := unsafe { s.str + s.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
|
||||
return pret
|
||||
|
||||
// return nr of bytes
|
||||
return pret.ret
|
||||
}
|
||||
|
||||
// parse_request_path sets the `path` and `method` fields
|
||||
[inline]
|
||||
pub fn (mut r Request) parse_request_path(s string) int {
|
||||
method_len := usize(0)
|
||||
path_len := usize(0)
|
||||
pub fn (mut r Request) parse_request_path(s string) !int {
|
||||
mut buf := s.str
|
||||
buf_end := unsafe { s.str + s.len }
|
||||
|
||||
pret := C.phr_parse_request_path(&char(s.str), s.len, voidptr(&r.method.str), &method_len,
|
||||
voidptr(&r.path.str), &path_len)
|
||||
if pret > 0 {
|
||||
unsafe {
|
||||
r.method = tos(r.method.str, int(method_len))
|
||||
r.path = tos(r.path.str, int(path_len))
|
||||
}
|
||||
mut pret := Pret{}
|
||||
r.phr_parse_request_path(buf, buf_end, mut pret)
|
||||
if pret.ret == -1 {
|
||||
return error(pret.err)
|
||||
}
|
||||
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]
|
||||
pub fn (mut r Request) parse_request_path_pipeline(s string) int {
|
||||
method_len := usize(0)
|
||||
path_len := usize(0)
|
||||
pub fn (mut r Request) parse_request_path_pipeline(s string) !int {
|
||||
mut buf := unsafe { s.str + r.prev_len }
|
||||
buf_end := unsafe { s.str + s.len }
|
||||
|
||||
pret := C.phr_parse_request_path_pipeline(&char(s.str), s.len, voidptr(&r.method.str),
|
||||
&method_len, voidptr(&r.path.str), &path_len)
|
||||
if pret > 0 {
|
||||
unsafe {
|
||||
r.method = tos(r.method.str, int(method_len))
|
||||
r.path = tos(r.path.str, int(path_len))
|
||||
}
|
||||
mut pret := Pret{}
|
||||
r.phr_parse_request_path_pipeline(buf, buf_end, mut pret)
|
||||
if pret.ret == -1 {
|
||||
return error(pret.err)
|
||||
}
|
||||
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 {
|
||||
r.write_string('Date: ')
|
||||
unsafe {
|
||||
r.buf += cpy(r.buf, r.date, 29)
|
||||
C.memcpy(r.buf, r.date, 29)
|
||||
r.buf += 29
|
||||
}
|
||||
r.write_string('\r\n')
|
||||
return unsafe { r }
|
||||
|
@ -78,7 +79,7 @@ pub fn (mut r Response) json() &Response {
|
|||
pub fn (mut r Response) body(body string) {
|
||||
r.write_string('Content-Length: ')
|
||||
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(body)
|
||||
|
@ -107,7 +108,8 @@ pub fn (mut r Response) raw(response string) {
|
|||
[inline]
|
||||
pub fn (mut r Response) end() int {
|
||||
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 n
|
||||
|
|
|
@ -23,21 +23,26 @@ enum Char_parse_state {
|
|||
}
|
||||
|
||||
// v_printf prints a sprintf-like formated `string` to the terminal.
|
||||
[deprecated: 'use string interpolation instead']
|
||||
fn v_printf(str string, pt ...voidptr) {
|
||||
print(v_sprintf(str, ...pt))
|
||||
// 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.
|
||||
[unsafe]
|
||||
pub fn v_printf(str string, pt ...voidptr) {
|
||||
print(unsafe { v_sprintf(str, ...pt) })
|
||||
}
|
||||
|
||||
// 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:
|
||||
// ```v
|
||||
// x := 3.141516
|
||||
// assert strconv.v_sprintf('aaa %G', x) == 'aaa 3.141516'
|
||||
// ```
|
||||
[deprecated: 'use string interpolation instead']
|
||||
[deprecated_after: '2023-06-30']
|
||||
[direct_array_access; manualfree]
|
||||
[direct_array_access; manualfree; unsafe]
|
||||
pub fn v_sprintf(str string, pt ...voidptr) string {
|
||||
mut res := strings.new_builder(pt.len * 16)
|
||||
defer {
|
||||
|
|
|
@ -19,7 +19,7 @@ fn event(e &tui.Event, x voidptr) {
|
|||
}
|
||||
|
||||
fn frame(x voidptr) {
|
||||
mut app := &App(x)
|
||||
mut app := unsafe { &App(x) }
|
||||
|
||||
app.tui.clear()
|
||||
app.tui.set_bg_color(r: 63, g: 81, b: 181)
|
||||
|
|
|
@ -86,6 +86,10 @@ pub enum FormatDelimiter {
|
|||
no_delimiter
|
||||
}
|
||||
|
||||
pub fn Time.new(t Time) Time {
|
||||
return new_time(t)
|
||||
}
|
||||
|
||||
// smonth returns month name abbreviation.
|
||||
pub fn (t Time) smonth() string {
|
||||
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
|
||||
struct C.timespec {
|
||||
pub struct C.timespec {
|
||||
mut:
|
||||
tv_sec 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 {
|
||||
return match expr {
|
||||
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 {
|
||||
typ_sym := t.sym(typ)
|
||||
if typ_sym.kind == .struct_ {
|
||||
if t.sym(typ).kind == .struct_ {
|
||||
return false
|
||||
} else {
|
||||
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 {
|
||||
if t.kind == .struct_ {
|
||||
info := t.info as Struct
|
||||
return info.is_heap
|
||||
if t.info is Struct {
|
||||
return t.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 {
|
||||
return false
|
||||
}
|
||||
|
@ -1154,10 +1176,10 @@ pub fn (t &Table) type_to_str(typ Type) string {
|
|||
}
|
||||
|
||||
// type name in code (for builtin)
|
||||
pub fn (mytable &Table) type_to_code(t Type) string {
|
||||
match t {
|
||||
ast.int_literal_type, ast.float_literal_type { return mytable.sym(t).kind.str() }
|
||||
else { return mytable.type_to_str_using_aliases(t, map[string]string{}) }
|
||||
pub fn (t &Table) type_to_code(typ Type) string {
|
||||
match typ {
|
||||
ast.int_literal_type, ast.float_literal_type { return t.sym(typ).kind.str() }
|
||||
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) {
|
||||
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() {
|
||||
node_basename := $if windows { 'node.exe' } $else { 'node' }
|
||||
os.find_abs_path_of_executable(node_basename) or {
|
||||
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 {
|
||||
go_basename := $if windows { 'go.exe' } $else { 'go' }
|
||||
os.find_abs_path_of_executable(go_basename) or {
|
||||
|
@ -96,8 +128,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
|
|||
} else {
|
||||
compiled_file
|
||||
}
|
||||
mut run_args := []string{cap: b.pref.run_args.len + 1}
|
||||
if b.pref.backend.is_js() {
|
||||
if b.pref.backend.is_js() || b.pref.backend == .wasm {
|
||||
run_args << compiled_file
|
||||
} else if b.pref.backend == .golang {
|
||||
run_args << ['run', compiled_file]
|
||||
|
|
|
@ -227,7 +227,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
|
|||
}
|
||||
} else {
|
||||
// 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) {
|
||||
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()
|
||||
&& right_sym.kind != .placeholder && left_sym.kind != .interface_
|
||||
if !is_blank_ident && right_sym.kind != .placeholder && left_sym.kind != .interface_
|
||||
&& !right_type.has_flag(.generic) && !left_type.has_flag(.generic) {
|
||||
// Dual sides check (compatibility check)
|
||||
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
|
||||
if left_type_unwrapped.is_ptr() && right_type_unwrapped.is_int()
|
||||
&& 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
|
||||
// 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 pos := token.Pos{} // and its position
|
||||
mut explicit_lock_needed := false
|
||||
mut expr := unsafe { expr_ }
|
||||
match mut expr {
|
||||
ast.CastExpr {
|
||||
// TODO
|
||||
return '', expr.pos
|
||||
}
|
||||
ast.ComptimeSelector {
|
||||
mut expr_left := expr.left
|
||||
if mut expr.left is ast.Ident {
|
||||
if mut expr.left.obj is ast.Var {
|
||||
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 !expr.obj.is_mut && !c.pref.translated && !c.file.is_translated
|
||||
&& !c.inside_unsafe {
|
||||
c.error('`${expr.name}` is immutable, declare it with `mut` to make it mutable',
|
||||
expr.pos)
|
||||
if c.inside_anon_fn {
|
||||
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
|
||||
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',
|
||||
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 {
|
||||
to_lock, pos = c.fail_if_immutable(expr.expr)
|
||||
to_lock, pos = c.fail_if_immutable(mut expr.expr)
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .mul && expr.right is ast.Ident {
|
||||
// Do not fail if dereference is immutable:
|
||||
// `*x = foo()` doesn't modify `x`
|
||||
} else {
|
||||
to_lock, pos = c.fail_if_immutable(expr.right)
|
||||
to_lock, pos = c.fail_if_immutable(mut expr.right)
|
||||
}
|
||||
}
|
||||
ast.PostfixExpr {
|
||||
to_lock, pos = c.fail_if_immutable(expr.expr)
|
||||
to_lock, pos = c.fail_if_immutable(mut expr.expr)
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
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',
|
||||
expr.pos)
|
||||
}
|
||||
to_lock, pos = c.fail_if_immutable(expr.expr)
|
||||
to_lock, pos = c.fail_if_immutable(mut expr.expr)
|
||||
}
|
||||
if to_lock != '' {
|
||||
// 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)
|
||||
return '', expr.pos
|
||||
}
|
||||
c.fail_if_immutable(expr.expr)
|
||||
c.fail_if_immutable(mut expr.expr)
|
||||
}
|
||||
.sum_type {
|
||||
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)
|
||||
return '', expr.pos
|
||||
}
|
||||
c.fail_if_immutable(expr.expr)
|
||||
c.fail_if_immutable(mut expr.expr)
|
||||
}
|
||||
.array, .string {
|
||||
// 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 {
|
||||
c.fail_if_immutable(expr.expr)
|
||||
c.fail_if_immutable(mut expr.expr)
|
||||
}
|
||||
else {
|
||||
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 {
|
||||
// TODO: should only work for builtin method
|
||||
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 != '' {
|
||||
// No automatic lock for array slicing (yet(?))
|
||||
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 {
|
||||
typ := c.expr(mut io.expr)
|
||||
if output {
|
||||
c.fail_if_immutable(io.expr)
|
||||
c.fail_if_immutable(mut io.expr)
|
||||
}
|
||||
if io.alias != '' {
|
||||
aliases << io.alias
|
||||
|
@ -2943,9 +2948,9 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type {
|
|||
else {}
|
||||
}
|
||||
}
|
||||
if from_type == ast.voidptr_type_idx && !c.inside_unsafe {
|
||||
// TODO make this an error
|
||||
c.warn('cannot cast voidptr to a struct outside `unsafe`', node.pos)
|
||||
if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated
|
||||
&& !c.file.is_translated {
|
||||
c.error('cannot cast voidptr to a struct outside `unsafe`', node.pos)
|
||||
}
|
||||
if !from_type.is_int() && final_from_sym.kind != .enum_
|
||||
&& !from_type.is_any_kind_of_pointer() {
|
||||
|
@ -3825,7 +3830,7 @@ fn (c &Checker) has_return(stmts []ast.Stmt) ?bool {
|
|||
[inline]
|
||||
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
|
||||
&& ((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) {
|
||||
|
|
|
@ -409,6 +409,11 @@ fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
|
|||
expecting_interface_map := map_value_sym.kind == .interface_
|
||||
//
|
||||
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 {
|
||||
if i == 0 && !use_expected_type {
|
||||
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())
|
||||
}
|
||||
}
|
||||
if val_type == ast.none_type && val0_type.has_flag(.option) {
|
||||
continue
|
||||
}
|
||||
if !c.check_types(val_type, val0_type)
|
||||
|| val0_type.has_flag(.option) != val_type.has_flag(.option)
|
||||
|| (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)
|
||||
}
|
||||
}
|
||||
} 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
|
||||
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)
|
||||
}
|
||||
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 ast.StructInit {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
left_type := c.expr(mut node.left)
|
||||
if left_type == ast.void_type {
|
||||
|
@ -1744,7 +1752,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
|
|||
arg.pos)
|
||||
}
|
||||
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 {
|
||||
tok := arg.share.str()
|
||||
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)
|
||||
}
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
tok := arg.share.str()
|
||||
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)
|
||||
}
|
||||
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) {
|
||||
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' {
|
||||
c.fail_if_immutable(node.left)
|
||||
c.fail_if_immutable(mut node.left)
|
||||
if node.args.len != 1 {
|
||||
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',
|
||||
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
|
||||
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
|
||||
if method_name == 'pop' {
|
||||
c.fail_if_immutable(node.left)
|
||||
c.fail_if_immutable(mut node.left)
|
||||
node.receiver_type = left_type.ref()
|
||||
} else {
|
||||
node.receiver_type = left_type
|
||||
}
|
||||
} 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))
|
||||
if method := c.table.find_method(unwrapped_left_sym, method_name) {
|
||||
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
|
||||
if node.branches.len > 0 && node.has_else {
|
||||
stmts := node.branches[0].stmts
|
||||
if stmts.len > 0 && stmts.last() is ast.ExprStmt
|
||||
&& (stmts.last() as ast.ExprStmt).typ != ast.void_type {
|
||||
if stmts.len > 0 && stmts.last() is ast.ExprStmt && stmts.last().typ != ast.void_type {
|
||||
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 (node.left is ast.Ident && 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
|
||||
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`
|
||||
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_sym := c.table.sym(c.unwrap_generic(left_value_type))
|
||||
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 {
|
||||
// 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() {
|
||||
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 &&
|
||||
(node.left as ast.InfixExpr).op == .inc) ||
|
||||
(node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) {
|
||||
if (node.left is ast.InfixExpr && node.left.op == .inc) ||
|
||||
(node.right is ast.InfixExpr && node.right.op == .inc) {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
if (node.cond is ast.Ident && 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.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}`)',
|
||||
node.pos)
|
||||
} else {
|
||||
node.auto_locked, _ = c.fail_if_immutable(node.expr)
|
||||
node.auto_locked, _ = c.fail_if_immutable(mut node.expr)
|
||||
}
|
||||
node.typ = 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',
|
||||
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 {
|
||||
got_type_sym := c.table.sym(got_type)
|
||||
exp_type_sym := c.table.sym(exp_type)
|
||||
|
@ -203,8 +208,11 @@ fn (mut c Checker) return_stmt(mut node ast.Return) {
|
|||
}
|
||||
continue
|
||||
}
|
||||
if exp_type_sym.kind == .array_fixed && got_type_sym.kind == .array_fixed {
|
||||
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 {
|
||||
exp_final_sym := c.table.final_sym(exp_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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 | }
|
|
@ -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??"
|
||||
}
|
|
@ -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 | }()
|
|
@ -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
|
||||
3 | f1 := fn [a] () {
|
||||
4 | a++
|
||||
|
@ -12,7 +12,7 @@ vlib/v/checker/tests/closure_immutable.vv:7:16: error: original `a` is immutable
|
|||
| ^
|
||||
8 | 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
|
||||
12 | f3 := fn [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 (
|
||||
2 | Red = 1
|
||||
| ~~~
|
||||
|
|
|
@ -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 |
|
|
@ -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 {
|
||||
2 | fld any
|
||||
| ~~~
|
||||
|
|
|
@ -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 | }
|
|
@ -0,0 +1,7 @@
|
|||
fn main() {
|
||||
println(return_item())
|
||||
}
|
||||
|
||||
fn return_item() int {
|
||||
return $tmpl('./templates/template.md')
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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 | }
|
|
@ -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
|
||||
g.assign_op = node.op
|
||||
g.inside_assign = true
|
||||
g.assign_ct_type = 0
|
||||
defer {
|
||||
g.assign_op = .unknown
|
||||
g.inside_assign = false
|
||||
g.assign_ct_type = 0
|
||||
}
|
||||
op := if is_decl { token.Kind.assign } else { node.op }
|
||||
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)
|
||||
}
|
||||
left.obj.typ = var_type
|
||||
g.assign_ct_type = var_type
|
||||
}
|
||||
} else if val is ast.ComptimeSelector {
|
||||
key_str := g.get_comptime_selector_key_type(val)
|
||||
|
@ -252,11 +255,13 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
|||
} else {
|
||||
val_type = g.comptime_var_type_map[key_str] or { var_type }
|
||||
}
|
||||
g.assign_ct_type = var_type
|
||||
}
|
||||
} else if val is ast.ComptimeCall {
|
||||
key_str := '${val.method_name}.return_type'
|
||||
var_type = g.comptime_var_type_map[key_str] or { 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 {
|
||||
val_info := (val as ast.Ident).info
|
||||
gen_or = val.or_expr.kind != .absent
|
||||
|
@ -271,6 +276,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
|||
val_type = var_type
|
||||
left.obj.typ = var_type
|
||||
}
|
||||
g.assign_ct_type = var_type
|
||||
} else if val is ast.IndexExpr {
|
||||
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))
|
||||
|
@ -278,6 +284,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
|||
var_type = ctyp
|
||||
val_type = 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 }
|
||||
}
|
||||
}
|
||||
g.assign_ct_type = var_type
|
||||
} else if mut left is ast.IndexExpr && val is ast.ComptimeSelector {
|
||||
key_str := g.get_comptime_selector_key_type(val)
|
||||
if key_str != '' {
|
||||
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 is_fixed_array_init := false
|
||||
|
@ -569,9 +578,22 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
|||
}
|
||||
}
|
||||
if !is_used_var_styp {
|
||||
if !val_type.has_flag(.option) && left_sym.info is ast.ArrayFixed
|
||||
&& left_sym.info.is_fn_ret {
|
||||
g.write('${styp[3..]} ')
|
||||
if !val_type.has_flag(.option) && left_sym.is_array_fixed() {
|
||||
if left_sym.kind == .alias {
|
||||
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 {
|
||||
g.write('${styp} ')
|
||||
}
|
||||
|
@ -692,8 +714,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
|
|||
// var = &auto_heap_var
|
||||
old_is_auto_heap := g.is_option_auto_heap
|
||||
g.is_option_auto_heap = val_type.has_flag(.option) && val is ast.PrefixExpr
|
||||
&& val.right is ast.Ident
|
||||
&& ((val as ast.PrefixExpr).right as ast.Ident).is_auto_heap()
|
||||
&& val.right is ast.Ident && (val.right as ast.Ident).is_auto_heap()
|
||||
defer {
|
||||
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 {
|
||||
sym := g.table.sym(left.left_type)
|
||||
sym := g.table.sym(g.table.unaliased_type(left.left_type))
|
||||
if sym.kind == .array {
|
||||
info := sym.info as ast.Array
|
||||
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 {
|
||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}());')
|
||||
} else if val_sym.kind == .string {
|
||||
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});')
|
||||
if val_typ.has_flag(.option) {
|
||||
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' {
|
||||
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));')
|
||||
} else if val_sym.kind in [.f32, .f64] {
|
||||
tmp_val := '*(${val_styp}*)DenseArray_value(&m.key_values, i)'
|
||||
if val_sym.kind == .f32 {
|
||||
g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32(tmp_val)});')
|
||||
if val_typ.has_flag(.option) {
|
||||
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 {
|
||||
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 {
|
||||
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});')
|
||||
} else {
|
||||
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}')
|
||||
|
|
|
@ -150,11 +150,12 @@ mut:
|
|||
loop_depth int
|
||||
ternary_names map[string]string
|
||||
ternary_level_names map[string][]string
|
||||
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
|
||||
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
|
||||
right_is_opt bool // right hand side on assignment is an option
|
||||
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
|
||||
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
|
||||
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
|
||||
empty_line bool
|
||||
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
|
||||
// if one location changes
|
||||
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 := ''
|
||||
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_ {
|
||||
styp = '${c.option_name}_${base.replace(' ', '_')}'
|
||||
} else {
|
||||
|
@ -1086,6 +1090,9 @@ fn (mut g Gen) result_type_name(t ast.Type) (string, string) {
|
|||
}
|
||||
mut styp := ''
|
||||
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_ {
|
||||
styp = '${c.result_name}_${base.replace(' ', '_')}'
|
||||
} 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 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}[]) { ')
|
||||
} else {
|
||||
g.write('_option_none(&(${styp}[]) { ')
|
||||
|
@ -4018,7 +4025,7 @@ fn (mut g Gen) map_init(node ast.MapInit) {
|
|||
}
|
||||
if value_sym.kind == .sum_type {
|
||||
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)
|
||||
} else {
|
||||
g.expr(expr)
|
||||
|
@ -4194,13 +4201,13 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) {
|
|||
[inline]
|
||||
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
|
||||
&& ((node as ast.Ident).obj as ast.Var).ct_type_var == .generic_param
|
||||
&& (node.obj as ast.Var).ct_type_var == .generic_param
|
||||
}
|
||||
|
||||
[inline]
|
||||
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
|
||||
&& ((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) {
|
||||
|
@ -4753,7 +4760,7 @@ fn (mut g Gen) return_stmt(node ast.Return) {
|
|||
fn_return_is_multi := sym.kind == .multi_return
|
||||
fn_return_is_option := fn_ret_type.has_flag(.option)
|
||||
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
|
||||
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))
|
||||
if fn_ret_type.has_flag(.generic) && fn_return_is_fixed_array {
|
||||
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
|
||||
|| 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 {
|
||||
g.or_expr_return_type = return_type.clear_flags(.option, .result)
|
||||
if g.inside_or_block {
|
||||
g.writeln('\terr = ${cvar_name}.err;')
|
||||
} else {
|
||||
g.writeln('\tIError err = ${cvar_name}.err;')
|
||||
}
|
||||
g.writeln('\tIError err = ${cvar_name}.err;')
|
||||
|
||||
g.inside_or_block = true
|
||||
defer {
|
||||
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
|
||||
}
|
||||
mut is_fixed_arr_ret := false
|
||||
if dump_sym.kind == .function {
|
||||
fninfo := dump_sym.info as ast.FnType
|
||||
str_dumparg_type = 'DumpFNType_${name}'
|
||||
|
@ -109,14 +110,30 @@ fn (mut g Gen) dump_expr_definitions() {
|
|||
g.go_back(str_tdef.len)
|
||||
dump_typedefs['typedef ${str_tdef};'] = true
|
||||
str_dumparg_ret_type = str_dumparg_type
|
||||
} else if !typ.has_flag(.option) && dump_sym.kind == .array_fixed {
|
||||
if (dump_sym.info as ast.ArrayFixed).is_fn_ret {
|
||||
str_dumparg_ret_type = str_dumparg_type
|
||||
str_dumparg_type = str_dumparg_type.trim_string_left('_v_')
|
||||
} else {
|
||||
// fixed array returned from function
|
||||
str_dumparg_ret_type = '_v_' + str_dumparg_type
|
||||
} else if !typ.has_flag(.option) && dump_sym.is_array_fixed() {
|
||||
match dump_sym.kind {
|
||||
.array_fixed {
|
||||
if (dump_sym.info as ast.ArrayFixed).is_fn_ret {
|
||||
str_dumparg_ret_type = str_dumparg_type
|
||||
str_dumparg_type = str_dumparg_type.trim_string_left('_v_')
|
||||
} 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 {
|
||||
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_rune(&sb, '\\n');")
|
||||
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()
|
||||
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}));')
|
||||
|
|
|
@ -225,8 +225,16 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
|||
mut name := g.c_fn_name(node)
|
||||
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}'
|
||||
} 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
|
||||
|
@ -683,9 +691,12 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
|||
tmp_var := g.new_tmp_var()
|
||||
fn_type := g.fn_var_signature(node.left.decl.return_type, node.left.decl.params.map(it.typ),
|
||||
tmp_var)
|
||||
line := g.go_before_stmt(0).trim_space()
|
||||
g.empty_line = true
|
||||
g.write('${fn_type} = ')
|
||||
g.expr(node.left)
|
||||
g.writeln(';')
|
||||
g.write(line)
|
||||
g.write(tmp_var)
|
||||
} else if node.or_block.kind == .absent {
|
||||
g.expr(node.left)
|
||||
|
@ -754,7 +765,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
|||
ret_typ = unaliased_type
|
||||
}
|
||||
}
|
||||
styp := g.typ(ret_typ)
|
||||
mut styp := g.typ(ret_typ)
|
||||
if gen_or && !is_gen_or_and_assign_rhs {
|
||||
cur_line = g.go_before_stmt(0)
|
||||
}
|
||||
|
@ -764,6 +775,9 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
|||
g.indent++
|
||||
g.write('${tmp_opt} = ')
|
||||
} 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} = ')
|
||||
if node.left is ast.AnonFn {
|
||||
g.expr(node.left)
|
||||
|
@ -1281,7 +1295,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
|
|||
receiver_type_name = 'map'
|
||||
}
|
||||
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)) {
|
||||
// `array_Xyz_clone` => `array_clone`
|
||||
receiver_type_name = 'array'
|
||||
|
|
|
@ -35,8 +35,17 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||
g.infix_expr_arithmetic_op(node)
|
||||
}
|
||||
.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)
|
||||
}
|
||||
.right_shift {
|
||||
g.write('(')
|
||||
g.gen_plain_infix_expr(node)
|
||||
g.write(')')
|
||||
}
|
||||
.and, .logical_or {
|
||||
g.infix_expr_and_or_op(node)
|
||||
}
|
||||
|
@ -846,7 +855,9 @@ fn (mut g Gen) infix_expr_left_shift_op(node ast.InfixExpr) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
g.write('(')
|
||||
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 }),
|
||||
utyp, utyp, 'res')
|
||||
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 {
|
||||
if sym.kind == .struct_ {
|
||||
|
@ -148,8 +151,13 @@ ${enc_fn_dec} {
|
|||
parent_typ := a.parent_type
|
||||
psym := g.table.sym(parent_typ)
|
||||
if is_js_prim(g.typ(parent_typ)) {
|
||||
g.gen_json_for_type(parent_typ)
|
||||
g.gen_prim_enc_dec(parent_typ, mut enc, mut dec)
|
||||
if utyp.has_flag(.option) {
|
||||
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 {
|
||||
enc.writeln('\to = cJSON_CreateObject();')
|
||||
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)
|
||||
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))
|
||||
} else if utyp.has_flag(.option) {
|
||||
g.gen_option_enc_dec(utyp, mut enc, mut dec)
|
||||
} else {
|
||||
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_ok(&res, (${result_name}*)&ret, sizeof(res));')
|
||||
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}')
|
||||
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);')
|
||||
|
||||
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]
|
||||
|
@ -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()
|
||||
gen_js_get(styp, tmp, name, mut dec, is_required)
|
||||
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});')
|
||||
if field.has_default_expr {
|
||||
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}')
|
||||
} else if field_sym.kind == .alias {
|
||||
alias := field_sym.info as ast.Alias
|
||||
parent_type := g.typ(alias.parent_type)
|
||||
parent_dec_name := js_dec_name(parent_type)
|
||||
if is_js_prim(parent_type) {
|
||||
parent_type := if field.typ.has_flag(.option) {
|
||||
alias.parent_type.set_flag(.option)
|
||||
} 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()
|
||||
gen_js_get(styp, tmp, name, mut dec, is_required)
|
||||
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}')
|
||||
} else {
|
||||
g.gen_json_for_type(alias.parent_type)
|
||||
g.gen_json_for_type(parent_type)
|
||||
tmp := g.new_tmp_var()
|
||||
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
|
||||
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 field_sym.kind == .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_ {
|
||||
|
@ -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' {
|
||||
// time struct requires special treatment
|
||||
// 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 {
|
||||
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 {
|
||||
arg_prefix := if field.typ.is_ptr() { '' } else { '*' }
|
||||
sptr_value := '${prefix_enc}${op}${c_name(field.name)}'
|
||||
if !field.typ.has_flag(.option) {
|
||||
enc.writeln('${indent}\tif (${sptr_value} != 0) {')
|
||||
enc.writeln('${indent}\t\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${arg_prefix}${sptr_value}));')
|
||||
enc.writeln('${indent}\t}\n')
|
||||
enc.writeln('${indent}if (${sptr_value} != 0) {')
|
||||
enc.writeln('${indent}\tcJSON_AddItemToObject(o, "${name}", ${enc_name}(${arg_prefix}${sptr_value}));')
|
||||
enc.writeln('${indent}}\n')
|
||||
} 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]
|
||||
&& (node.cond !is ast.Ident || (node.cond is ast.Ident
|
||||
&& node.cond.or_expr.kind == .absent))) || (node.cond is ast.SelectorExpr
|
||||
&& node.cond.or_block.kind == .absent
|
||||
&& ((node.cond as ast.SelectorExpr).expr !is ast.CallExpr
|
||||
|| ((node.cond as ast.SelectorExpr).expr as ast.CallExpr).or_block.kind == .absent)) {
|
||||
&& node.cond.or_block.kind == .absent && (node.cond.expr !is ast.CallExpr
|
||||
|| (node.cond.expr as ast.CallExpr).or_block.kind == .absent)) {
|
||||
cond_var = g.expr_string(node.cond)
|
||||
} else {
|
||||
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
|
||||
|
||||
import v.ast
|
||||
import v.token
|
||||
import v.gen.wasm.binaryen
|
||||
import wasm
|
||||
|
||||
const (
|
||||
type_none = binaryen.typenone()
|
||||
type_auto = binaryen.typeauto()
|
||||
type_i32 = binaryen.typeint32()
|
||||
type_i64 = binaryen.typeint64()
|
||||
type_f32 = binaryen.typefloat32()
|
||||
type_f64 = binaryen.typefloat64()
|
||||
)
|
||||
pub fn (mut g Gen) as_numtype(a wasm.ValType) wasm.NumType {
|
||||
if a in [.funcref_t, .externref_t, .v128_t] {
|
||||
g.w_error("as_numtype: called with '${a}'")
|
||||
}
|
||||
|
||||
return unsafe { wasm.NumType(a) }
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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_)
|
||||
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() {
|
||||
g.needs_stack = true
|
||||
return wasm.type_i32
|
||||
if typ.is_ptr() || typ.is_pointer() {
|
||||
return wasm.ValType.i32_t
|
||||
}
|
||||
if typ in ast.number_type_idxs {
|
||||
return match typ {
|
||||
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.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 {
|
||||
wasm.type_i64
|
||||
ast.i64_type_idx, ast.u64_type_idx {
|
||||
wasm.ValType.i64_t
|
||||
}
|
||||
ast.f32_type_idx {
|
||||
wasm.type_f32
|
||||
wasm.ValType.f32_t
|
||||
}
|
||||
ast.f64_type_idx, ast.float_literal_type_idx {
|
||||
wasm.type_f64
|
||||
ast.f64_type_idx {
|
||||
wasm.ValType.f64_t
|
||||
}
|
||||
else {
|
||||
wasm.type_i32
|
||||
wasm.ValType.i32_t
|
||||
}
|
||||
}
|
||||
}
|
||||
if typ == ast.bool_type_idx {
|
||||
return wasm.type_i32
|
||||
return wasm.ValType.i32_t
|
||||
}
|
||||
ts := g.table.sym(typ)
|
||||
match ts.info {
|
||||
ast.Struct {
|
||||
g.get_type_size_align(typ)
|
||||
return wasm.type_i32 // pointer
|
||||
}
|
||||
ast.MultiReturn {
|
||||
// TODO: cache??
|
||||
mut paraml := ts.info.types.map(g.get_wasm_type(it))
|
||||
return binaryen.typecreate(paraml.data, paraml.len)
|
||||
g.pool.type_size(typ)
|
||||
return wasm.ValType.i32_t // pointer
|
||||
}
|
||||
ast.Alias {
|
||||
return g.get_wasm_type(ts.info.parent_type)
|
||||
}
|
||||
ast.ArrayFixed {
|
||||
return wasm.type_i32
|
||||
return wasm.ValType.i32_t // pointer
|
||||
}
|
||||
ast.Enum {
|
||||
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}")
|
||||
}
|
||||
|
||||
fn infix_kind_return_bool(op token.Kind) bool {
|
||||
return op in [.eq, .ne, .gt, .lt, .ge, .le]
|
||||
}
|
||||
|
||||
fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) binaryen.Op {
|
||||
wasm_typ := g.get_wasm_type(typ)
|
||||
|
||||
match wasm_typ {
|
||||
wasm.type_i32 {
|
||||
match op {
|
||||
.plus {
|
||||
return binaryen.addint32()
|
||||
}
|
||||
.minus {
|
||||
return binaryen.subint32()
|
||||
}
|
||||
.mul {
|
||||
return binaryen.mulint32()
|
||||
}
|
||||
.mod {
|
||||
if typ.is_signed() {
|
||||
return binaryen.remsint32()
|
||||
} else {
|
||||
return binaryen.remuint32()
|
||||
}
|
||||
}
|
||||
.div {
|
||||
if typ.is_signed() {
|
||||
return binaryen.divsint32()
|
||||
} else {
|
||||
return binaryen.divuint32()
|
||||
}
|
||||
}
|
||||
.eq {
|
||||
return binaryen.eqint32()
|
||||
}
|
||||
.ne {
|
||||
return binaryen.neint32()
|
||||
}
|
||||
.gt {
|
||||
if typ.is_signed() {
|
||||
return binaryen.gtsint32()
|
||||
} else {
|
||||
return binaryen.gtuint32()
|
||||
}
|
||||
}
|
||||
.lt {
|
||||
if typ.is_signed() {
|
||||
return binaryen.ltsint32()
|
||||
} else {
|
||||
return binaryen.ltuint32()
|
||||
}
|
||||
}
|
||||
.ge {
|
||||
if typ.is_signed() {
|
||||
return binaryen.gesint32()
|
||||
} else {
|
||||
return binaryen.geuint32()
|
||||
}
|
||||
}
|
||||
.le {
|
||||
if typ.is_signed() {
|
||||
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 {}
|
||||
pub fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) {
|
||||
if g.is_param_type(typ) {
|
||||
eprintln(*g.table.sym(typ))
|
||||
panic('unimplemented')
|
||||
}
|
||||
|
||||
wasm_typ := g.as_numtype(g.get_wasm_type(typ))
|
||||
|
||||
match op {
|
||||
.plus {
|
||||
g.func.add(wasm_typ)
|
||||
}
|
||||
.minus {
|
||||
g.func.sub(wasm_typ)
|
||||
}
|
||||
.mul {
|
||||
g.func.mul(wasm_typ)
|
||||
}
|
||||
.mod {
|
||||
g.func.rem(wasm_typ, typ.is_signed())
|
||||
}
|
||||
.div {
|
||||
g.func.div(wasm_typ, typ.is_signed())
|
||||
}
|
||||
.eq {
|
||||
g.func.eq(wasm_typ)
|
||||
}
|
||||
.ne {
|
||||
g.func.ne(wasm_typ)
|
||||
}
|
||||
.gt {
|
||||
g.func.gt(wasm_typ, typ.is_signed())
|
||||
}
|
||||
.lt {
|
||||
g.func.lt(wasm_typ, typ.is_signed())
|
||||
}
|
||||
.ge {
|
||||
g.func.ge(wasm_typ, typ.is_signed())
|
||||
}
|
||||
.le {
|
||||
g.func.le(wasm_typ, typ.is_signed())
|
||||
}
|
||||
.xor {
|
||||
g.func.b_xor(wasm_typ)
|
||||
}
|
||||
.pipe {
|
||||
g.func.b_or(wasm_typ)
|
||||
}
|
||||
.amp {
|
||||
g.func.b_and(wasm_typ)
|
||||
}
|
||||
.left_shift {
|
||||
g.func.b_shl(wasm_typ)
|
||||
}
|
||||
.right_shift {
|
||||
g.func.b_shr(wasm_typ, true)
|
||||
}
|
||||
.unsigned_right_shift {
|
||||
g.func.b_shr(wasm_typ, false)
|
||||
}
|
||||
else {
|
||||
g.w_error('bad infix: op `${op}`')
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
fn static_arrays() {
|
||||
fn static_arrays() (int, int, i64) {
|
||||
a := [8]int{}
|
||||
b := [10, 12, 150]!
|
||||
c := [TEST{}, TEST{
|
||||
b: 10
|
||||
}]!
|
||||
|
||||
return a[2], b[1], c[1].b
|
||||
}
|
||||
|
||||
fn index_expression() {
|
||||
|
@ -16,7 +18,11 @@ fn index_expression() {
|
|||
|
||||
a := b[2]
|
||||
c := 'hello'[4]
|
||||
d := c'hello'[2]
|
||||
d := unsafe { c'hello'[2] }
|
||||
|
||||
println(a)
|
||||
println(c)
|
||||
println(d)
|
||||
}
|
||||
|
||||
fn test_this(index int) int {
|
||||
|
@ -31,9 +37,31 @@ struct AA {
|
|||
a [10]&int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn test_stuff() &int {
|
||||
a := AA{}
|
||||
|
||||
mut b := &int(0)
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
print('hello!')
|
||||
println('hello!')
|
||||
panic('nooo!')
|
||||
}
|
||||
|
||||
fn str_methods() {
|
||||
|
@ -16,3 +15,19 @@ fn str_implicit() {
|
|||
a := 100
|
||||
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!')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
hello!hello!
|
||||
128-192322
|
||||
false
|
||||
false
|
||||
true
|
||||
110
|
|
@ -59,15 +59,17 @@ fn addcfor() int {
|
|||
return val
|
||||
}
|
||||
|
||||
fn labelcfor() {
|
||||
fn labelcfor() (int, int) {
|
||||
mut idx := 0
|
||||
mut val := 0
|
||||
|
||||
hello: for {
|
||||
for {
|
||||
val++
|
||||
if val == 10 {
|
||||
continue hello
|
||||
}
|
||||
val++
|
||||
idx++
|
||||
|
||||
if val == 100 {
|
||||
break hello
|
||||
|
@ -75,6 +77,8 @@ fn labelcfor() {
|
|||
}
|
||||
break
|
||||
}
|
||||
|
||||
return val, idx
|
||||
}
|
||||
|
||||
fn infcfor() int {
|
||||
|
@ -89,3 +93,32 @@ fn infcfor() int {
|
|||
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
fn enums() {
|
||||
fn enums() Hello {
|
||||
mut a := Hello.a
|
||||
a = .c
|
||||
return a
|
||||
}
|
||||
|
||||
struct AA {
|
||||
|
@ -19,6 +20,7 @@ struct AA {
|
|||
fn of() {
|
||||
a := __offsetof(AA, b)
|
||||
b := sizeof(AA)
|
||||
_, _ := a, b
|
||||
}
|
||||
|
||||
fn constant() int {
|
||||
|
@ -55,3 +57,35 @@ fn ptr_arith() {
|
|||
}
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -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