Compare commits

...

37 Commits

Author SHA1 Message Date
Swastik Baranwal a9a94cfd51
checker: add a separate error msg for `fail_if_immutable` for anon fns (#18854) 2023-07-13 16:55:06 +03:00
yuyi aef4367a27
cgen: fix returning an option tuple - `fn f() ?(int,int) { return g() }` (#18851) 2023-07-13 12:02:26 +03:00
Felipe Pena 413ffbfc3b
v: allow alias as fixed array on return (#18817) 2023-07-13 11:58:49 +03:00
Felipe Pena 029e8a815b
checker: fix type checker on auto deref var (#18842) 2023-07-13 11:56:11 +03:00
Turiiya efcb15d05b
docs: update deprecated function results (#18850) 2023-07-13 01:43:04 +03:00
yuyi 52a055b6bc
cgen: fix option map with fn type value (#18849) 2023-07-12 15:48:04 +03:00
l-m c422919481
wasm: remove dependency on thirdparty/binaryen, webassembly backend rewrite (#18120) 2023-07-12 15:24:38 +03:00
Swastik Baranwal 1c7df29bed
checker: disallow voidptr cast to struct (#18845) 2023-07-12 11:07:34 +03:00
Casper Kuethe a43064af07
picoev, picohttparser: reimplement in V (#18506) 2023-07-12 09:40:16 +03:00
yuyi 045adb6600
parser: change warn to error, for const names with upper letter (fix #18838) (#18840) 2023-07-11 17:48:53 +03:00
phoebe 87dd5de191
docs: fix url location of asm_test.amd64.v (#18841) 2023-07-11 14:55:43 +03:00
JalonSolov 72cd9b80a3
ci: add v-analyzer builds (#18835) 2023-07-11 14:50:15 +03:00
yuyi 6b792b1257
v: use autocasting in complex conditions (#18839) 2023-07-11 14:49:43 +03:00
Felipe Pena 6b29d628c3
cgen: fix generated code for returning generic result/option to comptime var (#18834) 2023-07-10 21:24:13 +03:00
yuyi 6a8a22891d
checker: fix autocast in complex if condtions 5 (#18833) 2023-07-10 21:22:13 +03:00
Delyan Angelov acd581add5
strconv: mark strconv.v_sprintf and strconv.v_printf with [unsafe] (part 2, breaking change, needed an update to vsl) (#18836) 2023-07-10 21:20:47 +03:00
Delyan Angelov 6b00685629
strconv: remove deprecations for strconv.v_sprintf and strconv.v_printf (part 1) 2023-07-10 17:42:29 +03:00
Delyan Angelov e7af25ec14
examples: add examples/gg/many_thousands_of_circles.v, thanks to @xjunko, document how to use `ctx.end(how:.passthru)`. (#18832) 2023-07-10 15:33:14 +03:00
Felipe Pena f8e89ae91c
all: remove any type future implementation reference (#18822) 2023-07-10 10:41:06 +03:00
yuyi c9e8dd56c2
cgen: fix cross assign with aliased array (#18830) 2023-07-10 10:40:48 +03:00
yuyi 1728e4c73e
cgen: fix anon fn direct call with option (#18827) 2023-07-10 00:15:25 +03:00
Wertzui123 921a2e1c2e
delete empty log.txt (#18829) 2023-07-10 00:14:53 +03:00
Swastik Baranwal 0498f4c40f
checker: add an error for `$tmpl` function type mismatches (#18826) 2023-07-09 19:40:10 +03:00
Felipe Pena 59eb76c81d
v: allow `none` for not first values on map initialization (#18821) 2023-07-09 15:41:24 +03:00
Felipe Pena 8f3a1751e3
json: fix option state (#18802) 2023-07-09 08:23:24 +03:00
Alexander Medvednikov 5355c67ebe vweb: document live reload 2023-07-08 19:29:27 +03:00
Felipe Pena 4f518c2850
cgen: fix dump of map with option value fix (fix #18806) (#18813) 2023-07-08 07:07:24 +03:00
Delyan Angelov 54635185c4
cgen: ensure that `<<` and `>>` has higher precedence in the generated C code, than arithmetic operations (diff between C and V precedences) (#18814) 2023-07-08 07:02:32 +03:00
yuyi 17b576227f
checker: change 'fail_if_immutable(expr_ ast.Expr)' to 'fail_if_immutable(mut expr ast.Expr)' (#18811) 2023-07-07 23:27:52 +03:00
kbkpbot b3a6b73306
eventbus: add generic support for event name (#18805) 2023-07-07 22:33:57 +03:00
yuyi 97a726b188
cgen: fix nested or expr call (fix #18803) (#18807) 2023-07-07 22:06:10 +03:00
Felipe Pena 7fe794a974
json: fix option alias support (#18801) 2023-07-07 22:03:41 +03:00
encyclopaedia e7e5a07aa2
arrays: add more util functions and tests for them - find_first, find_last, join_to_string (#18784) 2023-07-07 06:52:08 +03:00
okk 7d6e15fa66
net.ftp: fix dir() for file names, which contain spaces (fix #18800) (#18804) 2023-07-07 06:50:20 +03:00
Delyan Angelov ded6c38061
vlib: add a new module `builtin.wchar`, to ease dealing with C APIs that accept `wchar_t*` (#18794) 2023-07-07 02:40:11 +03:00
Felipe Pena de392003be
cgen: fix code generation for array.clear (#18792) 2023-07-07 00:28:06 +03:00
yuyi 11f06e41c0
ast: clean up in types.v (#18793) 2023-07-07 00:27:21 +03:00
133 changed files with 5951 additions and 6690 deletions

View File

@ -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: |

View File

@ -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

View File

@ -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/

View File

@ -24,8 +24,6 @@ const (
'crypto.rand',
'os.bare',
'os2',
'picohttpparser',
'picoev',
'szip',
'v.eval',
]

0
cmd/tools/install_wabt.vsh Normal file → Executable file
View File

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -0,0 +1,56 @@
module main
import gg
import gx
import rand
const max_circles_per_pass = 1000
// prepare some random colors ahead of time
const colors = []gx.Color{len: max_circles_per_pass, init: gx.Color{
r: u8(index * 0 + rand.u8())
g: u8(index * 0 + rand.u8())
b: u8(index * 0 + rand.u8())
}}
fn frame(mut ctx gg.Context) {
// First pass, just clears the background:
ctx.begin()
ctx.end()
// We want to draw thousands of circles, but sokol has a limit for how
// many primitives can be in a single pass, and if you reach that limit
// you will not see *anything at all* for that pass.
// For the circles below, that limit is ~2520 circles per pass.
// To overcome this, we will use several passes instead, where each one
// will draw just 1000 circles.
// In other words, in total we will have 4 * 1000 = 4000 circles, drawn with
// 4 passes.
for i := 0; i < 4 * max_circles_per_pass; i += max_circles_per_pass {
ctx.begin()
for c in 0 .. max_circles_per_pass {
rx := rand.int_in_range(0, ctx.window.width) or { 0 }
ry := rand.int_in_range(0, ctx.window.height) or { 0 }
ctx.draw_circle_filled(rx, ry, 10, colors[c])
}
ctx.end(how: .passthru)
}
// The last pass, is for the fps overlay, that should be *always on top of everything*.
// Drawing it in a separate pass, guarantees, that it *will* be drawn, even if the drawing
// of all the other passes fail. Try increasing max_circles_per_pass to 3000 for example.
ctx.begin()
ctx.show_fps()
ctx.end(how: .passthru)
}
fn main() {
mut ctx := gg.new_context(
window_title: 'Many Thousands of Circles'
bg_color: gx.black
width: 600
height: 400
frame_fn: frame
)
ctx.run()
}

View File

@ -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()
}

View File

View File

@ -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()
}

View File

@ -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'
}) == ''
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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`.

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -0,0 +1,116 @@
module wchar
import strings
#include <wchar.h>
[typedef]
struct C.wchar_t {}
// Character is a type, that eases working with the platform dependent C.wchar_t type.
// Note: the size of C.wchar_t varies between platforms, it is 2 bytes on windows,
// and usually 4 bytes elsewhere.
pub type Character = C.wchar_t
// zero is a Character, that in C L"" strings represents the string end character (terminator).
pub const zero = from_rune(0)
// return a string representation of the given Character
pub fn (a Character) str() string {
return a.to_rune().str()
}
// == is an equality operator, to ease comparing Characters
// TODO: the default == operator, that V generates, does not work for C.wchar_t .
[inline]
pub fn (a Character) == (b Character) bool {
return u64(a) == u64(b)
}
// to_rune creates a V rune, given a Character
[inline]
pub fn (c Character) to_rune() rune {
return unsafe { *(&rune(&c)) }
}
// from_rune creates a Character, given a V rune
[inline]
pub fn from_rune(r rune) Character {
return unsafe { *(&Character(&r)) }
}
// length_in_characters returns the length of the given wchar_t* wide C style L"" string.
// Example: assert unsafe { wchar.length_in_characters(wchar.from_string('abc')) } == 3
// See also `length_in_bytes` .
[unsafe]
pub fn length_in_characters(p voidptr) int {
mut len := 0
pc := &Character(p)
for unsafe { pc[len] != wchar.zero } {
len++
}
return len
}
// length_in_bytes returns the length of the given wchar_t* wide C style L"" string in bytes.
// Note that the size of wchar_t is different on the different platforms, thus the length in
// bytes for the same data converted from UTF-8 to a &Character buffer, will be different as well.
// i.e. unsafe { wchar.length_in_bytes(wchar.from_string('abc')) } will be 12 on unix, but
// 6 on windows.
[unsafe]
pub fn length_in_bytes(p voidptr) int {
return unsafe { length_in_characters(p) } * int(sizeof(Character))
}
// to_string creates a V string, encoded in UTF-8, given a wchar_t*
// wide C style L"" string. It relies that the string has a 0 terminator at its end,
// to determine the string's length.
// Note, that the size of wchar_t is platform-dependent, and is *2 bytes* on windows,
// while it is *4 bytes* on most everything else.
// Unless you are interfacing with a C library, that does specifically use `wchar_t`,
// consider using `string_from_wide` instead, which will always assume that the input
// data is in an UTF-16 encoding, no matter what the platform is.
[unsafe]
pub fn to_string(p voidptr) string {
unsafe {
len := length_in_characters(p)
return to_string2(p, len)
}
}
// to_string2 creates a V string, encoded in UTF-8, given a `C.wchar_t*`
// wide C style L"" string. Note, that the size of `C.wchar_t` is platform-dependent,
// and is *2 bytes* on windows, while *4* on most everything else.
// Unless you are interfacing with a C library, that does specifically use wchar_t,
// consider using string_from_wide2 instead, which will always assume that the input
// data is in an UTF-16 encoding, no matter what the platform is.
[manualfree; unsafe]
pub fn to_string2(p voidptr, len int) string {
pc := &Character(p)
mut sb := strings.new_builder(len)
defer {
unsafe { sb.free() }
}
for i := 0; i < len; i++ {
u := unsafe { rune(pc[i]) }
sb.write_rune(u)
}
res := sb.str()
return res
}
// from_string converts the V string (in UTF-8 encoding), into a newly allocated
// platform specific buffer of C.wchar_t .
// The conversion is done by processing each rune of the input string 1 by 1.
[manualfree]
pub fn from_string(s string) &Character {
srunes := s.runes()
unsafe {
mut result := &Character(vcalloc_noscan((srunes.len + 1) * int(sizeof(Character))))
for i, r in srunes {
result[i] = from_rune(r)
}
result[srunes.len] = wchar.zero
return result
}
}

View File

@ -0,0 +1,36 @@
import builtin.wchar
const wide_serial_number_unix = [u16(67), 0, 76, 0, 52, 0, 54, 0, 73, 0, 49, 0, 65, 0, 48, 0, 48,
0, 54, 0, 52, 0, 57, 0, 0, 0, 0]
const wide_serial_number_windows = wide_serial_number_unix.map(u8(it))
const swide_serial_number = 'CL46I1A00649'
fn test_from_to_rune() {
for r in swide_serial_number.runes() {
c := wchar.from_rune(r)
assert c.to_rune() == r
}
assert wchar.from_rune(0).to_rune() == 0
}
fn test_to_string() {
mut p := voidptr(wide_serial_number_unix.data)
$if windows {
p = wide_serial_number_windows.data
}
assert unsafe { wchar.length_in_characters(p) } == swide_serial_number.len
s := unsafe { wchar.to_string(p) }
dump(s)
assert s == swide_serial_number
}
fn test_from_string() {
x := wchar.from_string(swide_serial_number)
assert unsafe { x[0] } == wchar.from_rune(`C`)
assert unsafe { x[1] } == wchar.from_rune(`L`)
assert unsafe { x[2] } == wchar.from_rune(`4`)
assert unsafe { x[11] } == wchar.from_rune(`9`)
assert unsafe { x[12] } == wchar.zero
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,41 @@
import json
struct Test {
optional_alias ?MyAlias // primitive
optional_struct ?MyAlias2 // complex
}
struct Complex {
a int = 3
}
type MyAlias = int
type MyAlias2 = Complex
fn test_empty() {
test := Test{}
encoded := json.encode(test)
assert dump(encoded) == '{}'
assert json.decode(Test, '{}')! == test
}
fn test_value() {
test := Test{
optional_alias: 1
}
encoded := json.encode(test)
assert dump(encoded) == '{"optional_alias":1}'
assert json.decode(Test, '{"optional_alias":1}')! == test
}
fn test_value_2() {
test := Test{
optional_alias: 1
optional_struct: Complex{
a: 1
}
}
encoded := json.encode(test)
assert dump(encoded) == '{"optional_alias":1,"optional_struct":{"a":1}}'
assert json.decode(Test, '{"optional_alias":1,"optional_struct":{"a":1}}')! == test
}

View File

@ -0,0 +1,46 @@
import json
struct Struct {
a int
}
struct Test {
a ?int
b ?string
c ?Struct
}
fn test_main() {
a := json.decode(Test, '{"a": 1, "b": "foo"}')!
dump(a)
assert a.a != none
assert a.b != none
b := json.decode(Test, '{"a": 1}')!
dump(b)
assert b.a != none
assert b.b == none
c := json.decode(Test, '{"a": 1, "b": null}')!
dump(b)
assert c.a != none
assert c.b == none
d := json.decode(Test, '{"a": null, "b": null}')!
dump(d)
assert d.a == none
assert d.b == none
e := json.decode(Test, '{"a": null, "b": null, "c": null}')!
dump(e)
assert e.a == none
assert e.b == none
assert e.c == none
f := json.decode(Test, '{"a": null, "b": null, "c": {"a":1}}')!
dump(f)
assert f.a == none
assert f.b == none
assert f.c != none
}

View File

@ -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

View File

@ -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".

View File

@ -0,0 +1,95 @@
module picoev
$if windows {
#include <winsock2.h>
#include <ws2tcpip.h>
} $else {
#include <sys/select.h>
}
pub struct SelectLoop {
mut:
id int
now i64
}
type LoopType = SelectLoop
// create_select_loop creates a `SelectLoop` struct with `id`
pub fn create_select_loop(id int) !&SelectLoop {
return &SelectLoop{
id: id
}
}
[direct_array_access]
fn (mut pv Picoev) update_events(fd int, events int) int {
// check if fd is in range
assert fd < max_fds
pv.file_descriptors[fd].events = u32(events & picoev_readwrite)
return 0
}
[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int {
readfds, writefds, errorfds := C.fd_set{}, C.fd_set{}, C.fd_set{}
// setup
C.FD_ZERO(&readfds)
C.FD_ZERO(&writefds)
C.FD_ZERO(&errorfds)
mut maxfd := 0
// find the maximum socket for `select` and add sockets to the fd_sets
for target in pv.file_descriptors {
if target.loop_id == pv.loop.id {
if target.events & picoev_read != 0 {
C.FD_SET(target.fd, &readfds)
if maxfd < target.fd {
maxfd = target.fd
}
}
if target.events & picoev_write != 0 {
C.FD_SET(target.fd, &writefds)
if maxfd < target.fd {
maxfd = target.fd
}
}
}
}
// select and handle sockets if any
tv := C.timeval{
tv_sec: u64(max_wait)
tv_usec: 0
}
r := C.@select(maxfd + 1, &readfds, &writefds, &errorfds, &tv)
if r == -1 {
// timeout
return -1
} else if r > 0 {
for target in pv.file_descriptors {
if target.loop_id == pv.loop.id {
// vfmt off
read_events := (
(if C.FD_ISSET(target.fd, &readfds) { picoev_read } else { 0 })
|
(if C.FD_ISSET(target.fd, &writefds) { picoev_write } else { 0 })
)
// vfmt on
if read_events != 0 {
$if trace_fd ? {
eprintln('do callback ${target.fd}')
}
// do callback!
unsafe { target.cb(target.fd, read_events, &pv) }
}
}
}
}
return 0
}

View File

@ -0,0 +1,203 @@
module picoev
#include <errno.h>
#include <sys/types.h>
#include <sys/event.h>
fn C.kevent(int, changelist voidptr, nchanges int, eventlist voidptr, nevents int, timout &C.timespec) int
fn C.kqueue() int
fn C.EV_SET(kev voidptr, ident int, filter i16, flags u16, fflags u32, data voidptr, udata voidptr)
pub struct C.kevent {
pub mut:
ident int
// uintptr_t
filter i16
flags u16
fflags u32
data voidptr
// intptr_t
udata voidptr
}
[heap]
pub struct KqueueLoop {
mut:
id int
now i64
kq_id int
// -1 if not changed
changed_fds int
events [1024]C.kevent
changelist [256]C.kevent
}
type LoopType = KqueueLoop
// create_kqueue_loop creates a new kernel event queue with loop_id=`id`
pub fn create_kqueue_loop(id int) !&KqueueLoop {
mut loop := &KqueueLoop{
id: id
}
loop.kq_id = C.kqueue()
if loop.kq_id == -1 {
return error('could not create kqueue loop!')
}
loop.changed_fds = -1
return loop
}
// ev_set sets a new `kevent` with file descriptor `index`
[inline]
pub fn (mut pv Picoev) ev_set(index int, operation int, events int) {
// vfmt off
filter := i16(
(if events & picoev_read != 0 { C.EVFILT_READ } else { 0 })
|
(if events & picoev_write != 0 { C.EVFILT_WRITE } else { 0 })
)
// vfmt on
C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0,
0)
}
// backend_build uses the lower 8 bits to store the old events and the higher 8
// bits to store the next file descriptor in `Target.backend`
[inline]
fn backend_build(next_fd int, events u32) int {
return int((u32(next_fd) << 8) | (events & 0xff))
}
// get the lower 8 bits
[inline]
fn backend_get_old_events(backend int) int {
return backend & 0xff
}
// get the higher 8 bits
[inline]
fn backend_get_next_fd(backend int) int {
return backend >> 8
}
// apply pending processes all changes for the file descriptors and updates `loop.changelist`
// if `aplly_all` is `true` the changes are immediately applied
fn (mut pv Picoev) apply_pending_changes(apply_all bool) int {
mut total, mut nevents := 0, 0
for pv.loop.changed_fds != -1 {
mut target := pv.file_descriptors[pv.loop.changed_fds]
old_events := backend_get_old_events(target.backend)
if target.events != old_events {
// events have been changed
if old_events != 0 {
pv.ev_set(total, C.EV_DISABLE, old_events)
total++
}
if target.events != 0 {
pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events))
total++
}
// Apply the changes if the total changes exceed the changelist size
if total + 1 >= pv.loop.changelist.len {
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL,
0, C.NULL)
assert nevents == 0
total = 0
}
}
pv.loop.changed_fds = backend_get_next_fd(target.backend)
target.backend = -1
}
if apply_all && total != 0 {
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL)
assert nevents == 0
total = 0
}
return total
}
[direct_array_access]
fn (mut pv Picoev) update_events(fd int, events int) int {
// check if fd is in range
assert fd < max_fds
mut target := pv.file_descriptors[fd]
// initialize if adding the fd
if events & picoev_add != 0 {
target.backend = -1
}
// return if nothing to do
if (events == picoev_del && target.backend == -1)
|| (events != picoev_del && events & picoev_readwrite == target.events) {
return 0
}
// add to changed list if not yet being done
if target.backend == -1 {
target.backend = backend_build(pv.loop.changed_fds, target.events)
pv.loop.changed_fds = fd
}
// update events
target.events = u32(events & picoev_readwrite)
// apply immediately if is a DELETE
if events & picoev_del != 0 {
pv.apply_pending_changes(true)
}
return 0
}
[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int {
ts := C.timespec{
tv_sec: max_wait
tv_nsec: 0
}
mut total, mut nevents := 0, 0
// apply changes later when the callback is called.
total = pv.apply_pending_changes(false)
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, pv.loop.events.len,
&ts)
if nevents == -1 {
// the errors we can only rescue
assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR
return -1
}
for i := 0; i < nevents; i++ {
event := pv.loop.events[i]
target := pv.file_descriptors[event.ident]
// changelist errors are fatal
assert event.flags & C.EV_ERROR == 0
if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 {
read_events := match int(event.filter) {
C.EVFILT_READ {
picoev_read
}
C.EVFILT_WRITE {
picoev_write
}
else {
0
}
}
// do callback!
unsafe { target.cb(target.fd, read_events, &pv) }
}
}
return 0
}

139
vlib/picoev/loop_linux.c.v Normal file
View File

@ -0,0 +1,139 @@
module picoev
#include <sys/epoll.h>
fn C.epoll_create(int) int
fn C.epoll_wait(int, voidptr, int, int) int
fn C.epoll_ctl(int, int, int, voidptr) int
[typedef]
pub union C.epoll_data_t {
mut:
ptr voidptr
fd int
u32 u32
u64 u64
}
[packed]
pub struct C.epoll_event {
mut:
events u32
data C.epoll_data_t
}
[heap]
pub struct EpollLoop {
mut:
id int
epoll_fd int
events [1024]C.epoll_event
now i64
}
type LoopType = EpollLoop
// create_epoll_loop creates a new epoll instance for and returns an
// `EpollLoop` struct with `id`
pub fn create_epoll_loop(id int) !&EpollLoop {
mut loop := &EpollLoop{
id: id
}
loop.epoll_fd = C.epoll_create(max_fds)
if loop.epoll_fd == -1 {
return error('could not create epoll loop!')
}
return loop
}
[direct_array_access]
fn (mut pv Picoev) update_events(fd int, events int) int {
// check if fd is in range
assert fd < max_fds
mut target := pv.file_descriptors[fd]
mut ev := C.epoll_event{}
// fd belongs to loop
if events & picoev_del != target.events && target.loop_id != pv.loop.id {
return -1
}
if events & picoev_readwrite == target.events {
return 0
}
// vfmt off
ev.events = u32(
(if events & picoev_read != 0 { C.EPOLLIN } else { 0 })
|
(if events & picoev_write != 0 { C.EPOLLOUT } else { 0 })
)
// vfmt on
ev.data.fd = fd
if events & picoev_del != 0 {
// nothing to do
} else if events & picoev_readwrite == 0 {
// delete the file if it exists
epoll_ret := C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_DEL, fd, &ev)
// check error
assert epoll_ret == 0
} else {
// change settings to 0
mut epoll_ret := C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_MOD, fd, &ev)
if epoll_ret != 0 {
// if the file is not present we want to add it
assert C.errno == C.ENOENT
epoll_ret = C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_ADD, fd, &ev)
// check error
assert epoll_ret == 0
}
}
// convert to u32?
target.events = u32(events)
return 0
}
[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int {
nevents := C.epoll_wait(pv.loop.epoll_fd, &pv.loop.events, max_fds, max_wait * 1000)
if nevents == -1 {
// timeout has occurred
return -1
}
for i := 0; i < nevents; i++ {
mut event := pv.loop.events[i]
target := unsafe { pv.file_descriptors[event.data.fd] }
unsafe {
assert event.data.fd < max_fds
}
if pv.loop.id == target.loop_id && target.events & picoev_readwrite != 0 {
// vfmt off
read_events := (
(if event.events & u32(C.EPOLLIN) != 0 { picoev_read } else { 0 })
|
(if event.events & u32(C.EPOLLOUT) != 0 { picoev_write } else { 0 })
)
// vfmt on
if read_events != 0 {
// do callback!
unsafe { target.cb(event.data.fd, read_events, &pv) }
}
} else {
// defer epoll delete
event.events = 0
unsafe {
C.epoll_ctl(pv.loop.epoll_fd, C.EPOLL_CTL_DEL, event.data.fd, &event)
}
}
}
return 0
}

203
vlib/picoev/loop_macos.c.v Normal file
View File

@ -0,0 +1,203 @@
module picoev
#include <errno.h>
#include <sys/types.h>
#include <sys/event.h>
fn C.kevent(int, changelist voidptr, nchanges int, eventlist voidptr, nevents int, timout &C.timespec) int
fn C.kqueue() int
fn C.EV_SET(kev voidptr, ident int, filter i16, flags u16, fflags u32, data voidptr, udata voidptr)
pub struct C.kevent {
pub mut:
ident int
// uintptr_t
filter i16
flags u16
fflags u32
data voidptr
// intptr_t
udata voidptr
}
[heap]
pub struct KqueueLoop {
mut:
id int
now i64
kq_id int
// -1 if not changed
changed_fds int
events [1024]C.kevent
changelist [256]C.kevent
}
type LoopType = KqueueLoop
// create_kqueue_loop creates a new kernel event queue with loop_id=`id`
pub fn create_kqueue_loop(id int) !&KqueueLoop {
mut loop := &KqueueLoop{
id: id
}
loop.kq_id = C.kqueue()
if loop.kq_id == -1 {
return error('could not create kqueue loop!')
}
loop.changed_fds = -1
return loop
}
// ev_set sets a new `kevent` with file descriptor `index`
[inline]
pub fn (mut pv Picoev) ev_set(index int, operation int, events int) {
// vfmt off
filter := i16(
(if events & picoev_read != 0 { C.EVFILT_READ } else { 0 })
|
(if events & picoev_write != 0 { C.EVFILT_WRITE } else { 0 })
)
// vfmt on
C.EV_SET(&pv.loop.changelist[index], pv.loop.changed_fds, filter, operation, 0, 0,
0)
}
// backend_build uses the lower 8 bits to store the old events and the higher 8
// bits to store the next file descriptor in `Target.backend`
[inline]
fn backend_build(next_fd int, events u32) int {
return int((u32(next_fd) << 8) | (events & 0xff))
}
// get the lower 8 bits
[inline]
fn backend_get_old_events(backend int) int {
return backend & 0xff
}
// get the higher 8 bits
[inline]
fn backend_get_next_fd(backend int) int {
return backend >> 8
}
// apply pending processes all changes for the file descriptors and updates `loop.changelist`
// if `aplly_all` is `true` the changes are immediately applied
fn (mut pv Picoev) apply_pending_changes(apply_all bool) int {
mut total, mut nevents := 0, 0
for pv.loop.changed_fds != -1 {
mut target := pv.file_descriptors[pv.loop.changed_fds]
old_events := backend_get_old_events(target.backend)
if target.events != old_events {
// events have been changed
if old_events != 0 {
pv.ev_set(total, C.EV_DISABLE, old_events)
total++
}
if target.events != 0 {
pv.ev_set(total, C.EV_ADD | C.EV_ENABLE, int(target.events))
total++
}
// Apply the changes if the total changes exceed the changelist size
if total + 1 >= pv.loop.changelist.len {
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL,
0, C.NULL)
assert nevents == 0
total = 0
}
}
pv.loop.changed_fds = backend_get_next_fd(target.backend)
target.backend = -1
}
if apply_all && total != 0 {
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, C.NULL, 0, C.NULL)
assert nevents == 0
total = 0
}
return total
}
[direct_array_access]
fn (mut pv Picoev) update_events(fd int, events int) int {
// check if fd is in range
assert fd < max_fds
mut target := pv.file_descriptors[fd]
// initialize if adding the fd
if events & picoev_add != 0 {
target.backend = -1
}
// return if nothing to do
if (events == picoev_del && target.backend == -1)
|| (events != picoev_del && events & picoev_readwrite == target.events) {
return 0
}
// add to changed list if not yet being done
if target.backend == -1 {
target.backend = backend_build(pv.loop.changed_fds, target.events)
pv.loop.changed_fds = fd
}
// update events
target.events = u32(events & picoev_readwrite)
// apply immediately if is a DELETE
if events & picoev_del != 0 {
pv.apply_pending_changes(true)
}
return 0
}
[direct_array_access]
fn (mut pv Picoev) poll_once(max_wait int) int {
ts := C.timespec{
tv_sec: max_wait
tv_nsec: 0
}
mut total, mut nevents := 0, 0
// apply changes later when the callback is called.
total = pv.apply_pending_changes(false)
nevents = C.kevent(pv.loop.kq_id, &pv.loop.changelist, total, &pv.loop.events, pv.loop.events.len,
&ts)
if nevents == -1 {
// the errors we can only rescue
assert C.errno == C.EACCES || C.errno == C.EFAULT || C.errno == C.EINTR
return -1
}
for i := 0; i < nevents; i++ {
event := pv.loop.events[i]
target := pv.file_descriptors[event.ident]
// changelist errors are fatal
assert event.flags & C.EV_ERROR == 0
if pv.loop.id == target.loop_id && event.filter & (C.EVFILT_READ | C.EVFILT_WRITE) != 0 {
read_events := match int(event.filter) {
C.EVFILT_READ {
picoev_read
}
C.EVFILT_WRITE {
picoev_write
}
else {
0
}
}
// do callback!
unsafe { target.cb(target.fd, read_events, &pv) }
}
}
return 0
}

View File

@ -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)
}
}

143
vlib/picoev/socket_util.c.v Normal file
View File

@ -0,0 +1,143 @@
module picoev
import net
import picohttpparser
#include <errno.h>
$if windows {
#include <winsock2.h>
#include <ws2tcpip.h>
} $else $if freebsd || macos {
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
} $else {
#include <netinet/tcp.h>
#include <sys/resource.h>
}
[inline]
fn get_time() i64 {
// time.now() is slow
return i64(C.time(C.NULL))
}
[inline]
fn accept(fd int) int {
return C.accept(fd, 0, 0)
}
[inline]
fn close_socket(fd int) {
$if trace_fd ? {
eprintln('close ${fd}')
}
$if windows {
C.closesocket(fd)
} $else {
C.close(fd)
}
}
[inline]
fn setup_sock(fd int) ! {
flag := 1
if C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_NODELAY, &flag, sizeof(int)) < 0 {
return error('setup_sock.setup_sock failed')
}
$if freebsd {
if C.fcntl(fd, C.F_SETFL, C.SOCK_NONBLOCK) != 0 {
return error('fcntl failed')
}
} $else $if windows {
non_blocking_mode := u32(1)
if C.ioctlsocket(fd, C.FIONBIO, &non_blocking_mode) == C.SOCKET_ERROR {
return error('icotlsocket failed')
}
} $else {
// linux and macos
if C.fcntl(fd, C.F_SETFL, C.O_NONBLOCK) != 0 {
return error('fcntl failed')
}
}
}
[inline]
fn req_read(fd int, b &u8, max_len int, idx int) int {
// use `recv` instead of `read` for windows compatibility
unsafe {
return C.recv(fd, b + idx, max_len - idx, 0)
}
}
fn fatal_socket_error(fd int) bool {
if C.errno == C.EAGAIN {
// try again later
return false
}
$if windows {
if C.errno == C.WSAEWOULDBLOCK {
// try again later
return false
}
} $else {
if C.errno == C.EWOULDBLOCK {
// try again later
return false
}
}
$if trace_fd ? {
eprintln('fatal error ${fd}: ${C.errno}')
}
return true
}
// listen creates a listening tcp socket and returns its file decriptor
fn listen(config Config) int {
// not using the `net` modules sockets, because not all socket options are defined
fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0)
assert fd != -1
$if trace_fd ? {
eprintln('listen: ${fd}')
}
// Setting flags for socket
flag := 1
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEADDR, &flag, sizeof(int)) == 0
$if linux {
// epoll socket options
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEPORT, &flag, sizeof(int)) == 0
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_QUICKACK, &flag, sizeof(int)) == 0
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_DEFER_ACCEPT, &config.timeout_secs,
sizeof(int)) == 0
queue_len := max_queue
assert C.setsockopt(fd, C.IPPROTO_TCP, C.TCP_FASTOPEN, &queue_len, sizeof(int)) == 0
}
// addr settings
mut addr := C.sockaddr_in{
sin_family: u8(C.AF_INET)
sin_port: C.htons(config.port)
sin_addr: C.htonl(C.INADDR_ANY)
}
size := sizeof(C.sockaddr_in)
bind_res := C.bind(fd, voidptr(unsafe { &net.Addr(&addr) }), size)
assert bind_res == 0
listen_res := C.listen(fd, C.SOMAXCONN)
assert listen_res == 0
setup_sock(fd) or {
config.err_cb(config.user_data, picohttpparser.Request{}, mut &picohttpparser.Response{},
err)
}
return fd
}

View File

@ -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."

View File

@ -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 }
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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 }

View File

@ -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{}) }
}
}

View File

@ -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]

View 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] {

View File

@ -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) {

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/auto_deref_assign_err.vv:10:6: error: cannot assign to `o`: expected `&int`, not `string`
8 | fn sub( mut o &int ) {
9 | println( 'in function got: ' + o.str() )
10 | o = "mutate int as string??"
| ~~~~~~~~~~~~~~~~~~~~~~~~
11 | }

View File

@ -0,0 +1,11 @@
fn main() {
mut a := 42
println( 'before sub: ' + a.str() )
sub( mut a )
println( 'after sub: ' + a.str() )
}
fn sub( mut o &int ) {
println( 'in function got: ' + o.str() )
o = "mutate int as string??"
}

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/closure_copy_immutable_var_err.vv:5:3: error: the closure copy of `name` is immutable, declare it with `mut` to make it mutable
3 |
4 | fn [name] () {
5 | name = 'Ivan'
| ~~~~
6 | println(name)
7 | }()

View File

@ -0,0 +1,8 @@
fn main() {
mut name := 'John'
fn [name] () {
name = 'Ivan'
println(name)
}()
}

View File

@ -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++

View File

@ -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
| ~~~

View File

@ -0,0 +1,34 @@
vlib/v/checker/tests/map_with_none_err.vv:2:6: warning: unused variable: `a`
1 | fn main() {
2 | mut a := {
| ^
3 | 'bar': none
4 | }
vlib/v/checker/tests/map_with_none_err.vv:6:6: warning: unused variable: `b`
4 | }
5 |
6 | mut b := {
| ^
7 | 'foo': 1,
8 | 'bar': none
vlib/v/checker/tests/map_with_none_err.vv:11:6: warning: unused variable: `c`
9 | }
10 |
11 | mut c := {
| ^
12 | 'foo': ?int(none),
13 | 'bar': none
vlib/v/checker/tests/map_with_none_err.vv:3:10: error: map value cannot be only `none`
1 | fn main() {
2 | mut a := {
3 | 'bar': none
| ~~~~
4 | }
5 |
vlib/v/checker/tests/map_with_none_err.vv:8:10: error: invalid map value: expected `int`, not `none`
6 | mut b := {
7 | 'foo': 1,
8 | 'bar': none
| ~~~~
9 | }
10 |

View File

@ -0,0 +1,15 @@
fn main() {
mut a := {
'bar': none
}
mut b := {
'foo': 1,
'bar': none
}
mut c := {
'foo': ?int(none),
'bar': none
}
}

View File

@ -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 | }

View File

@ -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]!
}

View File

@ -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
| ~~~

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/template_type_mismatch_err.vv:6:9: error: cannot use `string` as type `int` in return argument
4 |
5 | fn return_item() int {
6 | return $tmpl('./templates/template.md')
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7 | }

View File

@ -0,0 +1,7 @@
fn main() {
println(return_item())
}
fn return_item() int {
return $tmpl('./templates/template.md')
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/voidptr_cast_to_struct_err.vv:4:7: error: cannot cast `voidptr` to struct
2 |
3 | fn main() {
4 | a := Foo(unsafe { nil })
| ~~~~~~~~~~~~~~~~~~~
5 | println(a)
6 | }

View File

@ -0,0 +1,6 @@
struct Foo {}
fn main() {
a := Foo(unsafe { nil })
println(a)
}

View File

@ -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)

View File

@ -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}')

View File

@ -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

View File

@ -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}));')

View File

@ -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'

View File

@ -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(')')
}
}

View File

@ -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}));')
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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}`')
}

View File

@ -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)
}
}

View File

@ -0,0 +1,12 @@
import v.ast
import v.gen.wasm.serialise
fn test_alignment() {
table := ast.new_table()
mut pool := serialise.new_pool(table)
pool.append(ast.BoolLiteral{ val: true }, 0) // +0, +1
pool.append(ast.FloatLiteral{ val: '0' }, ast.f32_type) // +3, +4
pool.append(ast.BoolLiteral{ val: true }, 0) // +0, +1
assert pool.buf.len == 9
}

View File

@ -0,0 +1,449 @@
// Copyright (c) 2023 l-m.dev. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module serialise
import v.ast
// import v.eval
import math.bits
import strconv
[noinit]
pub struct Pool {
mut:
table &ast.Table
// eval eval.Eval
structs map[ast.Type]StructInfo
strings []StringInfo // string intern
pub:
null_terminated bool
intern_strings bool
store_relocs bool
pub mut:
buf []u8
relocs []Reloc
highest_alignment int
}
struct StringInfo {
pos int
len int
}
pub struct StructInfo {
pub mut:
offsets []int
}
pub struct Reloc {
pub:
pos int
offset int
}
pub fn (mut p Pool) type_struct_info(typ ast.Type) ?StructInfo {
ts := p.table.sym(typ)
if ts.info !is ast.Struct {
return none
}
if typ.idx() in p.structs {
return p.structs[typ.idx()]
}
// will cache inside `p.structs`
p.type_size(typ)
return p.structs[typ.idx()]
}
pub fn (mut p Pool) type_size(typ ast.Type) (int, int) {
ts := p.table.sym(typ)
if ts.size != -1 && typ.idx() in p.structs {
return ts.size, ts.align
}
if ts.info is ast.Enum {
return p.table.type_size(ts.info.typ)
}
if ts.info !is ast.Struct {
return p.table.type_size(typ)
}
ti := ts.info as ast.Struct
// code borrowed from native, inserted in wasm, and now here!
mut strc := StructInfo{}
mut size := 0
mut align := 1
for f in ti.fields {
f_size, f_align := p.type_size(f.typ)
if f_size == 0 {
strc.offsets << 0
continue
}
padding := (f_align - size % f_align) % f_align
strc.offsets << size + padding
size += f_size + padding
if f_align > align {
align = f_align
}
}
size = (size + align - 1) / align * align
p.structs[typ.idx()] = strc
mut ts_ := p.table.sym(typ)
ts_.size = size
ts_.align = align
return size, align
}
[params]
pub struct PoolOpts {
null_terminated bool = true
intern_strings bool = true
store_relocs bool = true
}
pub fn new_pool(table &ast.Table, opts PoolOpts) Pool {
return Pool{
table: table
null_terminated: opts.null_terminated
intern_strings: opts.intern_strings
store_relocs: opts.store_relocs
}
}
fn (mut p Pool) zero_fill(size int) {
// TODO: eventually support a way to utilise a BSS section
for i := 0; i < size; i++ {
p.buf << 0
}
}
fn (mut p Pool) alignment(align int) int {
if align > p.highest_alignment {
p.highest_alignment = align
}
padding := (align - p.buf.len % align) % align
p.zero_fill(padding)
pos := p.buf.len
return pos
}
/*
fn (mut p Pool) append_struct(init ast.StructInit) ?int {
old_len := p.buf.len
size, align := p.type_size(v.typ)
ts := g.table.sym(v.typ)
ts_info := ts.info as ast.Struct
pos := p.alignment(align)
if init.fields.len == 0 && !(ts_info.fields.any(it.has_default_expr)) {
for i := 0 ; i < size ; i++ {
p.buf << 0
}
return pos
}
/* for i, f in ts_info.fields {
field_to_be_set := init.fields.map(it.name).filter(f.name)
} */
/* for i, f in ts_info.fields {
field_to_be_set := init.fields.map(it.name).contains(f.name)
if !field_to_be_set {
offset := g.structs[v.typ.idx()].offsets[i]
offset_var := g.offset(v, f.typ, offset)
fsize, _ := g.get_type_size_align(f.typ)
if f.has_default_expr {
g.expr(f.default_expr, f.typ)
g.set(offset_var)
} else {
g.zero_fill(offset_var, fsize)
}
}
}
for f in init.fields {
field := ts.find_field(f.name) or {
g.w_error('could not find field `${f.name}` on init')
}
offset := g.structs[v.typ.idx()].offsets[field.i]
offset_var := g.offset(v, f.expected_type, offset)
g.expr(f.expr, f.expected_type)
g.set(offset_var)
} */
return pos
}*/
pub fn eval_escape_codes_raw(str string) !string {
mut buffer := []u8{}
mut i := 0
for i < str.len {
if str[i] != `\\` {
buffer << str[i]
i++
continue
}
// skip \
i++
match str[i] {
`\\`, `'`, `"` {
buffer << str[i]
i++
}
`a`, `b`, `f` {
buffer << str[i] - u8(90)
i++
}
`n` {
buffer << `\n`
i++
}
`r` {
buffer << `\r`
i++
}
`t` {
buffer << `\t`
i++
}
`u` {
i++
utf8 := strconv.parse_int(str[i..i + 4], 16, 16) or {
return error('invalid \\u escape code (${str[i..i + 4]})')
}
i += 4
buffer << u8(utf8)
buffer << u8(utf8 >> 8)
}
`v` {
buffer << `\v`
i++
}
`x` {
i++
c := strconv.parse_int(str[i..i + 2], 16, 8) or {
return error('invalid \\x escape code (${str[i..i + 2]})')
}
i += 2
buffer << u8(c)
}
`0`...`7` {
c := strconv.parse_int(str[i..i + 3], 8, 8) or {
return error('invalid escape code \\${str[i..i + 3]}')
}
i += 3
buffer << u8(c)
}
else {
return error('invalid escape code \\${str[i]}')
}
}
}
return buffer.bytestr()
}
pub fn eval_escape_codes(str_lit ast.StringLiteral) !string {
if str_lit.is_raw {
return str_lit.val
}
return eval_escape_codes_raw(str_lit.val)
}
pub fn (mut p Pool) append_string(val string) int {
data := val.bytes()
if p.intern_strings {
for str in p.strings {
if data.len > str.len || (p.null_terminated && data.len != str.len) {
continue
}
// TODO: aggressive string interning if `p.null_terminated`
if p.buf[str.pos..str.pos + data.len] == data {
return str.pos
}
}
}
pos := p.buf.len
p.buf << data
if p.null_terminated {
p.buf << 0
}
p.strings << StringInfo{
pos: pos
len: data.len
}
return pos
}
pub fn (mut p Pool) append(init ast.Expr, typ ast.Type) (int, bool) {
match init {
ast.BoolLiteral {
pos := p.buf.len
p.buf << u8(init.val)
return pos, true
}
ast.FloatLiteral {
assert typ.is_pure_float()
mut pos := 0
if typ == ast.f32_type {
pos = p.alignment(4)
p.u32(bits.f32_bits(init.val.f32()))
} else {
pos = p.alignment(8)
p.u64(bits.f64_bits(init.val.f64()))
}
return pos, true
}
ast.IntegerLiteral {
assert typ.is_pure_int()
size, align := p.table.type_size(typ)
pos := p.alignment(align)
match size {
1 {
p.u8(u8(init.val.i8()))
}
2 {
p.u16(u16(init.val.i16()))
}
4 {
p.u32(u32(init.val.int()))
}
8 {
p.u64(u64(init.val.i64()))
}
else {}
}
return pos, true
}
ast.CharLiteral {
// 3 extra bytes for improved program correctness, thank me later
rne := u32(eval_escape_codes_raw(init.val) or { panic('Pool.append: ${err}') }.runes()[0])
pos := p.alignment(4)
p.u32(rne)
return pos, true
}
ast.StringLiteral {
val := eval_escape_codes(init) or { panic('Pool.append: ${err}') }
str_pos := p.append_string(val)
if typ != ast.string_type {
// c'str'
return str_pos, true
}
_, align := p.type_size(ast.string_type)
tss := p.table.sym(ast.string_type).info as ast.Struct
pos := p.alignment(align)
for field in tss.fields {
match field.name {
'str' {
p.ptr(str_pos)
}
'len' {
p.u32(u32(val.len))
}
'is_lit' {
p.u32(1)
}
else {
panic('ast.string: field `${field.name}` is unknown')
}
}
}
return pos, true
}
else {
size, align := p.type_size(typ)
pos := p.alignment(align)
p.zero_fill(size)
return pos, false
}
}
}
fn (mut p Pool) u64(v u64) {
p.buf << u8(v)
p.buf << u8(v >> u64(8))
p.buf << u8(v >> u64(16))
p.buf << u8(v >> u64(24))
p.buf << u8(v >> u64(32))
p.buf << u8(v >> u64(40))
p.buf << u8(v >> u64(48))
p.buf << u8(v >> u64(56))
}
fn (mut p Pool) u32(v u32) {
p.buf << u8(v)
p.buf << u8(v >> u32(8))
p.buf << u8(v >> u32(16))
p.buf << u8(v >> u32(24))
}
fn (mut p Pool) u16(v u16) {
p.buf << u8(v)
p.buf << u8(v >> u32(8))
}
fn (mut p Pool) u8(v u8) {
p.buf << v
}
fn (mut p Pool) ptr(offset int) int {
assert p.table.pointer_size in [1, 2, 4, 8]
pos := p.buf.len // p.alignment(p.table.pointer_size)
if p.store_relocs {
p.relocs << Reloc{
pos: pos
offset: offset
}
}
match p.table.pointer_size {
1 {
p.u8(u8(offset))
}
2 {
p.u16(u16(offset))
}
4 {
p.u32(u32(offset))
}
8 {
p.u64(u64(offset))
}
else {}
}
return pos
}

View File

@ -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))
}

View File

@ -0,0 +1,29 @@
--- test printing numbers
-20
0
22
-1000000022
--- test powi
4611686018427387904
-1
0
--- test sqrti
123456789
12
0
--- test negate
-20
1
--- test inc
20
-1
--- test lcm
6
6
6
0
--- test gcd
3
3
3
0

View File

@ -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)
}

View File

@ -0,0 +1,14 @@
--- static_arrays()
0
12
10
--- index_expression()
150
111
108
--- test_this()
108
10
10
--- test_stuff()
0

View File

@ -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!')
}

View File

@ -0,0 +1,6 @@
hello!hello!
128-192322
false
false
true
110

View File

@ -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())
}

View File

@ -0,0 +1,19 @@
--- func()
10
0
0
--- test()
2
5
5
--- boolfor()
1
--- inffor()
1
--- addcfor()
45
--- labelcfor()
100
99
--- infcfor()
10

View File

@ -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))
}

View File

@ -0,0 +1,17 @@
ptr_arith
12
14
102
before defer
defer_if: start
defer_if: defer!
defer_if: start
defer!
constants
100
hello
888
enums
30
8

Some files were not shown because too many files have changed in this diff Show More