Compare commits

...

36 Commits

Author SHA1 Message Date
shove 65a493d023
v.util: fix a wrong path analysis when parsing 'mod_name' (fix #18970) (#19090) 2023-08-10 05:42:59 +03:00
jacksonmowry 76b4c92848
db.sqlite: make functions return results, breaking change (#19093) 2023-08-10 05:39:32 +03:00
Delyan Angelov d0cc564089
db.mysql: make mysql.Result.result public (fix #19098) 2023-08-10 05:21:44 +03:00
Alexander Medvednikov f915366ac4 checker: improve the nil fn error a bit 2023-08-09 22:37:11 +03:00
yuyi 3211a653c3
scanner: fix string interpolation with nested string interpolation in inner quotes 2 (#19094) 2023-08-09 15:05:17 +03:00
Delyan Angelov eef9b5f168
builtin,os: fix compiling V programs with latest clang 16 on windows (clang 16 is stricter than clang 14) (#19095) 2023-08-09 15:04:44 +03:00
Turiiya 64029a2980
vdoc: implement keyboard shortcuts for search navigation (#19088) 2023-08-09 13:53:15 +03:00
yuyi b7afe6b236
scanner: add error for invalid newline rune literal, make errors more informative (#19091) 2023-08-09 08:49:47 +03:00
Delyan Angelov 6813a12339
transformer: keep the symbolic expressions inside dump(expr) from being optimised out, even when they could be, when composed of literals known at comptime (#19086) 2023-08-08 18:25:55 +03:00
shove 10df697d32
time: add 'i', 'ii' in custom_format() for 12-hours clock(0-12-1-11) (#19083) 2023-08-08 12:25:39 +03:00
yuyi 68f18fcb8e
scanner: fix string interpolation with nested string interpolation in inner quotes (fix #19081) (#19085) 2023-08-08 12:25:05 +03:00
shove f4859ffb11
checker: fix missing or_block check for left expr of CallExpr(fix #19061) (#19074) 2023-08-08 09:06:03 +03:00
Turiiya 3b3395d93b
vpm: don't keep empty dirs for git installs (#19070) 2023-08-08 08:59:16 +03:00
Swastik Baranwal 8db1aaafd5
checker: explicitly disallow creating type aliases of `none`, i.e. `type Abc = none` (#19078) 2023-08-08 08:58:10 +03:00
Delyan Angelov 286d39706b
time: add a format_rfc3339_nano() method to time.Time 2023-08-08 08:35:05 +03:00
shove 320057df1c
os.notify: remove the meaningless [noinit] attribute of the notifier (#19075) 2023-08-07 09:19:54 +03:00
jacksonmowry 07b36d69f3
db.sqlite: add exec_param_many and exec_param methods (#19071) 2023-08-07 09:00:03 +03:00
yuyi 6045a1db02
cgen: minor cleanups in method_call() (#19068) 2023-08-07 07:09:39 +03:00
Everton J. Carpes 357ac0bb5a
examples: show how to call a simple v module from ruby (#19073) (#19073)
This is a copy/adaptation of the python example (#13105)
2023-08-07 07:07:00 +03:00
yuyi 7c2f3e4530
fmt: fix formatting of C.f(/*mut*/buff &char) i64 (#19069) 2023-08-07 07:00:35 +03:00
Rodrigo Villablanca 23a7c40c44
tools: remove panics in favor of errors in `v bump` (#19066) 2023-08-06 22:24:43 +03:00
yuyi c3f7fe39ec
checker: fix struct field fntype value call (#19067) 2023-08-06 13:18:48 +03:00
jhuntos 7ca23f6316
picoev: add initial values for struct field callbacks, to fix new compiler notices (#19065)
Missing initial value for struct values
2023-08-06 07:09:16 +03:00
Delyan Angelov b9a523cefd
time: store time with nanosecond resolution in time.Time, deprecate Time.microsecond, add utility methods and tests (#19062) 2023-08-05 23:41:23 +03:00
Subhomoy Haldar cc97b8df1e
tools: add support for skiping lines in `v bump` (#19064) 2023-08-05 23:00:40 +03:00
Turiiya 8e26ca3f5a
time: fix `'a'` and `'A'` in `custom_format` (#19060) 2023-08-05 13:58:16 +03:00
Alexander Medvednikov da7a9bc8ae ci: run users.v ui example 2023-08-05 09:51:56 +03:00
yuyi f72cb00b74
ast: fix formatting fn header with parameter comments (#19059) 2023-08-05 07:33:13 +03:00
Turiiya e5cd1724f9
time: fix `'h'`, `'hh'` in `custom_format` for 12pm (#19058) 2023-08-05 07:28:12 +03:00
shove 301320f4b0
os.notify: implement the kqueue backend for notify.FdNotifier (#19057) 2023-08-05 07:11:07 +03:00
yuyi 4cf8328f71
ast, fmt: simplify fmt.fn_decl() (#19054) 2023-08-04 16:54:16 +03:00
Delyan Angelov d91c7f1b3b
tools: use --filter=blob:none to reduce initial network trafic for most of the `git clone` commands, done by tools like oldv, gen_vc, fast_job, `v translate` etc 2023-08-04 11:50:30 +03:00
Lucas V. Araujo ffdd5bb955
net.mbedtls: fix an error with in_memory_verification (fix #19051) (#19052) 2023-08-04 01:32:33 +03:00
Delyan Angelov 5bb02b3dd7
tools: fix some noise in the output of `v test-all` 2023-08-03 23:19:14 +03:00
Delyan Angelov 598992b208
parser: support `const x := 123`, to make extracting locals as constants less annoying while prototyping 2023-08-03 23:10:33 +03:00
Delyan Angelov e3ade704cb
tools: remove remaining references to vlib/sqlite, vlib/mysql, vlib/pg (and make `v test-cleancode` fail *loudly*, when a folder is missing, regression after a421e485f). 2023-08-03 23:10:02 +03:00
101 changed files with 1473 additions and 579 deletions

View File

@ -97,6 +97,7 @@ jobs:
mkdir -p ~/.vmodules
ln -s $(pwd) ~/.vmodules/ui
../v examples/rectangles.v
../v examples/users.v
## ../v run examples/build_examples.vsh
- name: V self compilation with -usecache
run: |

View File

@ -36,7 +36,7 @@ fn main() {
if !os.exists('website') {
println('cloning the website repo...')
os.system('git clone git@github.com:/vlang/website.git')
os.system('git clone --filter=blob:none git@github.com:/vlang/website.git')
}
for {
elog('------------------- Checking for updates ... -------------------')

View File

@ -42,7 +42,7 @@ const (
// name
app_name = 'gen_vc'
// version
app_version = '0.1.2'
app_version = '0.1.3'
// description
app_description = "This tool regenerates V's bootstrap .c files every time the V master branch is updated."
// assume something went wrong if file size less than this
@ -233,8 +233,8 @@ fn (mut gen_vc GenVC) generate() {
// delete repos
gen_vc.purge_repos()
// clone repos
gen_vc.cmd_exec('git clone --depth 1 https://${git_repo_v} ${git_repo_dir_v}')
gen_vc.cmd_exec('git clone --depth 1 https://${git_repo_vc} ${git_repo_dir_vc}')
gen_vc.cmd_exec('git clone --filter=blob:none https://${git_repo_v} ${git_repo_dir_v}')
gen_vc.cmd_exec('git clone --filter=blob:none https://${git_repo_vc} ${git_repo_dir_vc}')
// get output of git log -1 (last commit)
git_log_v := gen_vc.cmd_exec('git -C ${git_repo_dir_v} log -1 --format="commit %H%nDate: %ci%nDate Unix: %ct%nSubject: %s"')
git_log_vc := gen_vc.cmd_exec('git -C ${git_repo_dir_vc} log -1 --format="Commit %H%nDate: %ci%nDate Unix: %ct%nSubject: %s"')

View File

@ -244,6 +244,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
if testing.github_job != 'ubuntu-tcc' {
skip_files << 'examples/c_interop_wkhtmltopdf.v' // needs installation of wkhtmltopdf from https://github.com/wkhtmltopdf/packaging/releases
skip_files << 'examples/call_v_from_python/test.v' // the example only makes sense to be compiled, when python is installed
skip_files << 'examples/call_v_from_ruby/test.v' // the example only makes sense to be compiled, when ruby is installed
// the ttf_test.v is not interactive, but needs X11 headers to be installed, which is done only on ubuntu-tcc for now
skip_files << 'vlib/x/ttf/ttf_test.v'
skip_files << 'vlib/vweb/vweb_app_test.v' // imports the `sqlite` module, which in turn includes sqlite3.h

View File

@ -82,7 +82,7 @@ pub fn clone_or_pull(remote_git_url string, local_worktree_path string) {
scripting.run('git -C "${local_worktree_path}" pull --quiet ')
} else {
// Clone a fresh
scripting.run('git clone --quiet "${remote_git_url}" "${local_worktree_path}" ')
scripting.run('git clone --filter=blob:none --quiet "${remote_git_url}" "${local_worktree_path}" ')
}
}

View File

@ -4,7 +4,7 @@ import scripting
import vgit
const (
tool_version = '0.0.3'
tool_version = '0.0.4'
tool_description = ' Checkout an old V and compile it as it was on specific commit.
| This tool is useful, when you want to discover when something broke.
| It is also useful, when you just want to experiment with an older historic V.
@ -83,7 +83,7 @@ fn sync_cache() {
repofolder := os.join_path(cache_oldv_folder, reponame)
if !os.exists(repofolder) {
scripting.verbose_trace(@FN, 'cloning to ${repofolder}')
scripting.exec('git clone --quiet https://github.com/vlang/${reponame} ${repofolder}') or {
scripting.exec('git clone --filter=blob:none --quiet https://github.com/vlang/${reponame} ${repofolder}') or {
scripting.verbose_trace(@FN, '## error during clone: ${err}')
exit(1)
}

View File

@ -4,7 +4,7 @@ import scripting
import vgit
const (
tool_version = '0.0.5'
tool_version = '0.0.6'
tool_description = " Compares V executable size and performance,
| between 2 commits from V's local git history.
| When only one commit is given, it is compared to master.
@ -39,7 +39,7 @@ fn (c Context) compare_versions() {
scripting.chdir(c.vgo.workdir)
scripting.run('rm -rf "${c.a}" "${c.b}" "${c.vc}" ')
// clone the VC source *just once per comparison*, and reuse it:
scripting.run('git clone --quiet "${c.vgo.vc_repo_url}" "${c.vc}" ')
scripting.run('git clone --filter=blob:none --quiet "${c.vgo.vc_repo_url}" "${c.vc}" ')
println('Comparing V performance of commit ${c.commit_before} (before) vs commit ${c.commit_after} (after) ...')
c.prepare_v(c.b, c.commit_before)
c.prepare_v(c.a, c.commit_after)

View File

@ -19,7 +19,7 @@ fn main() {
os.mkdir_all(vmodules)!
println('C2V is not installed. Cloning C2V to ${c2v_dir} ...')
os.chdir(vmodules)!
res := os.execute('git clone https://github.com/vlang/c2v')
res := os.execute('git clone --filter=blob:none https://github.com/vlang/c2v')
if res.exit_code != 0 {
eprintln('Failed to download C2V.')
exit(1)

View File

@ -9,7 +9,7 @@ import semver
const (
tool_name = os.file_name(os.executable())
tool_version = '0.0.1'
tool_version = '0.1.0'
tool_description = '\n Bump the semantic version of the v.mod and/or specified files.
The first instance of a version number is replaced with the new version.
@ -21,6 +21,11 @@ const (
version: \'0.2.42\'
VERSION = "1.23.8"
If certain lines need to be skipped, use the --skip option. For instance,
the following command will skip lines containing "tool-version":
v bump --patch --skip "tool-version" [files...]
Examples:
Bump the patch version in v.mod if it exists
v bump --patch
@ -37,6 +42,7 @@ struct Options {
major bool
minor bool
patch bool
skip string
}
type ReplacementFunction = fn (re regex.RE, input string, start int, end int) string
@ -67,10 +73,10 @@ fn get_replacement_function(options Options) ReplacementFunction {
return replace_with_increased_patch_version
}
fn process_file(input_file string, options Options) {
lines := os.read_lines(input_file) or { panic('Failed to read file: ${input_file}') }
fn process_file(input_file string, options Options) ! {
lines := os.read_lines(input_file) or { return error('Failed to read file: ${input_file}') }
mut re := regex.regex_opt(semver_query) or { panic('Could not create a RegEx parser.') }
mut re := regex.regex_opt(semver_query) or { return error('Could not create a RegEx parser.') }
repl_fn := get_replacement_function(options)
@ -85,7 +91,8 @@ fn process_file(input_file string, options Options) {
}
// Check if replacement is necessary
updated_line := if line.to_lower().contains('version') {
updated_line := if line.to_lower().contains('version') && !(options.skip != ''
&& line.contains(options.skip)) {
replacement_complete = true
re.replace_by_fn(line, repl_fn)
} else {
@ -103,11 +110,11 @@ fn process_file(input_file string, options Options) {
os.rm(backup_file) or {}
// Rename the original to the backup.
os.mv(input_file, backup_file) or { panic('Failed to copy file: ${input_file}') }
os.mv(input_file, backup_file) or { return error('Failed to copy file: ${input_file}') }
// Process the old file and write it back to the original.
os.write_file(input_file, new_lines.join_lines()) or {
panic('Failed to write file: ${input_file}')
return error('Failed to write file: ${input_file}')
}
// Remove the backup file.
@ -122,7 +129,7 @@ fn process_file(input_file string, options Options) {
fn main() {
if os.args.len < 2 {
println('Usage: ${tool_name} [options] [file1 file2 ...]
eprintln('Usage: ${tool_name} [options] [file1 file2 ...]
${tool_description}
Try ${tool_name} -h for more help...')
exit(1)
@ -141,6 +148,12 @@ Try ${tool_name} -h for more help...')
patch: fp.bool('patch', `p`, false, 'Bump the patch version.')
minor: fp.bool('minor', `n`, false, 'Bump the minor version.')
major: fp.bool('major', `m`, false, 'Bump the major version.')
skip: fp.string('skip', `s`, '', 'Skip lines matching this substring.').trim_space()
}
remaining := fp.finalize() or {
eprintln(fp.usage())
exit(1)
}
if options.show_help {
@ -148,24 +161,33 @@ Try ${tool_name} -h for more help...')
exit(0)
}
validate_options(options) or { panic(err) }
validate_options(options) or {
eprintln(fp.usage())
exit(1)
}
files := os.args[3..]
files := remaining[1..]
if files.len == 0 {
if !os.exists('v.mod') {
println('v.mod does not exist. You can create one using "v init".')
eprintln('v.mod does not exist. You can create one using "v init".')
exit(1)
}
process_file('v.mod', options) or {
eprintln('Failed to process v.mod: ${err}')
exit(1)
}
process_file('v.mod', options)
}
for input_file in files {
if !os.exists(input_file) {
println('File not found: ${input_file}')
eprintln('File not found: ${input_file}')
exit(1)
}
process_file(input_file, options) or {
eprintln('Failed to process ${input_file}: ${err}')
exit(1)
}
process_file(input_file, options)
}
}

View File

@ -78,7 +78,7 @@ fn run_individual_test(case BumpTestCase) ! {
os.rm(test_file) or {}
os.write_file(test_file, case.contents)!
//
os.execute_or_exit('${os.quoted_path(vexe)} bump --patch ${os.quoted_path(test_file)}')
patch_lines := os.read_lines(test_file)!
assert patch_lines[case.line] == case.expected_patch
@ -90,7 +90,7 @@ fn run_individual_test(case BumpTestCase) ! {
os.execute_or_exit('${os.quoted_path(vexe)} bump --major ${os.quoted_path(test_file)}')
major_lines := os.read_lines(test_file)!
assert major_lines[case.line] == case.expected_major
//
os.rm(test_file)!
}
@ -99,3 +99,65 @@ fn test_all_bump_cases() {
run_individual_test(case) or { panic(err) }
}
}
struct SkipTestCase {
file_name string
contents string
skip string
line int
expected_patch string
expected_minor string
expected_major string
}
const skip_test_cases = [
SkipTestCase{
file_name: 'CITATION.cff'
contents: 'abstract: A sample CLI tool made in V that prints geometric shapes to the screen.
authors:
- alias: hungrybluedev
family-names: Haldar
given-names: Subhomoy
cff-version: 1.2.0
date-released: 2023-04-20
license: MIT
message: Please cite this software using these information.
repository-code: https://github.com/hungrybluedev/geo
title: geo
url: https://github.com/hungrybluedev/geo
version: 0.2.4
'
line: 12
skip: 'cff-version'
expected_patch: 'version: 0.2.5'
expected_minor: 'version: 0.3.0'
expected_major: 'version: 1.0.0'
},
]
fn run_skip_test(case SkipTestCase) ! {
test_file := os.join_path_single(tfolder, case.file_name)
os.rm(test_file) or {}
os.write_file(test_file, case.contents)!
os.execute_or_exit('${os.quoted_path(vexe)} bump --patch --skip="${case.skip}" ${os.quoted_path(test_file)}')
patch_lines := os.read_lines(test_file)!
assert patch_lines[case.line] == case.expected_patch
os.execute_or_exit('${os.quoted_path(vexe)} bump --minor --skip="${case.skip}" ${os.quoted_path(test_file)}')
minor_lines := os.read_lines(test_file)!
assert minor_lines[case.line] == case.expected_minor
os.execute_or_exit('${os.quoted_path(vexe)} bump --major --skip="${case.skip}" ${os.quoted_path(test_file)}')
major_lines := os.read_lines(test_file)!
assert major_lines[case.line] == case.expected_major
os.rm(test_file)!
}
fn test_all_skip_bump_cases() ! {
for case in skip_test_cases {
run_skip_test(case) or { panic(err) }
}
}

View File

@ -197,9 +197,14 @@ body {
width: 1.2rem;
height: 1.2rem;
}
.doc-nav > .heading-container > .heading > #search {
.doc-nav #search {
position: relative;
margin: 0.6rem 1.2rem 1rem 1.2rem;
display: flex;
}
.doc-nav #search input {
border: none;
width: 100%;
border-radius: 0.2rem;
padding: 0.5rem 1rem;
outline: none;
@ -208,17 +213,32 @@ body {
color: #fff;
color: var(--menu-search-text-color);
}
.doc-nav > .heading-container > .heading > #search::placeholder {
.doc-nav #search input::placeholder {
color: #edf2f7;
text-transform: uppercase;
font-size: 12px;
font-weight: 600;
}
.doc-nav > .heading-container > .heading > #search:-ms-input-placeholder {
color: #edf2f7;
text-transform: uppercase;
font-size: 12px;
font-weight: 600;
.doc-nav #search-keys {
position: absolute;
height: 100%;
align-items: center;
display: flex;
top: 0;
right: 0.75rem;
opacity: 0.33;
transition: opacity 0.1s;
}
.doc-nav #search-keys.hide {
opacity: 0;
}
.doc-nav #search-keys kbd {
padding: 2.5px 3px;
margin-left: 1px;
font-size: 11px;
background-color: var(--menu-background-color);
border: 1px solid #ffffff44;
border-radius: 3px;
}
.doc-nav > .content {
padding: 0 2rem 2rem 2rem;
@ -278,7 +298,8 @@ body {
.doc-nav .search li {
line-height: 1.5;
}
.doc-nav > .search .result:hover {
.doc-nav > .search .result:hover,
.doc-nav > .search .result.selected {
background-color: #00000021;
background-color: var(--menu-search-result-background-hover-color);
}

View File

@ -49,7 +49,6 @@ function setupMobileToggle() {
const isHidden = docNav.classList.contains('hidden');
docNav.classList.toggle('hidden');
const search = docNav.querySelector('.search');
// console.log(search);
const searchHasResults = search.classList.contains('has-results');
if (isHidden && searchHasResults) {
search.classList.remove('mobile-hidden');
@ -145,6 +144,72 @@ function setupSearch() {
}
});
searchInput.addEventListener('input', onInputChange);
setupSearchKeymaps();
}
function setupSearchKeymaps() {
const searchInput = document.querySelector('#search input');
// Keyboard shortcut indicator
const searchKeys = document.createElement('div');
const modifierKeyPrefix = navigator.platform.includes('Mac') ? '⌘' : 'Ctrl';
searchKeys.setAttribute('id', 'search-keys');
searchKeys.innerHTML = '<kbd>' + modifierKeyPrefix + '</kbd><kbd>k</kbd>';
searchInput.parentElement?.appendChild(searchKeys);
searchInput.addEventListener('focus', () => searchKeys.classList.add('hide'));
searchInput.addEventListener('blur', () => searchKeys.classList.remove('hide'));
// Global shortcuts to focus searchInput
document.addEventListener('keydown', (ev) => {
if (ev.key === '/' || ((ev.ctrlKey || ev.metaKey) && ev.key === 'k')) {
ev.preventDefault();
searchInput.focus();
}
});
// Shortcuts while searchInput is focused
let selectedIdx = -1;
function selectResult(results, newIdx) {
if (selectedIdx !== -1) {
results[selectedIdx].classList.remove('selected');
}
results[newIdx].classList.add('selected');
results[newIdx].scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
selectedIdx = newIdx;
}
searchInput.addEventListener('keydown', (ev) => {
const searchResults = document.querySelectorAll('.search .result');
switch (ev.key) {
case 'Escape':
searchInput.blur();
break;
case 'Enter':
if (!searchResults.length || selectedIdx === -1) break;
searchResults[selectedIdx].querySelector('a').click();
break;
case 'ArrowDown':
ev.preventDefault();
if (!searchResults.length) break;
if (selectedIdx >= searchResults.length - 1) {
// Cycle to first if last is selected
selectResult(searchResults, 0);
} else {
// Select next
selectResult(searchResults, selectedIdx + 1);
}
break;
case 'ArrowUp':
ev.preventDefault();
if (!searchResults.length) break;
if (selectedIdx <= 0) {
// Cycle to last if first is selected (or select it if none is selcted yet)
selectResult(searchResults, searchResults.length - 1);
} else {
// Select previous
selectResult(searchResults, selectedIdx - 1);
}
break;
default:
selectedIdx = -1;
}
});
}
function createSearchResult(data) {
@ -194,11 +259,3 @@ function debounce(func, timeout) {
timer = setTimeout(next, timeout > 0 ? timeout : 300);
};
}
document.addEventListener('keypress', (ev) => {
if (ev.key == '/') {
const search = document.getElementById('search');
ev.preventDefault();
search.focus();
}
});

View File

@ -46,7 +46,9 @@
</div>
{{ menu_icon }}
</div>
<input type="text" id="search" placeholder="Search... (beta)" autocomplete="off" />
<div id="search">
<input type="text" placeholder="Search... (beta)" autocomplete="off" />
</div>
</div>
</div>
<nav class="search hidden"></nav>

View File

@ -249,7 +249,7 @@ fn (upd VlsUpdater) compile_from_source() ! {
if !os.exists(vls_src_folder) {
upd.log('Cloning VLS repo...')
clone_result := os.execute('${git} clone https://github.com/vlang/vls ${vls_src_folder}')
clone_result := os.execute('${git} clone --filter=blob:none https://github.com/vlang/vls ${vls_src_folder}')
if clone_result.exit_code != 0 {
return error('Failed to build VLS from source. Reason: ${clone_result.output}')
}
@ -483,7 +483,7 @@ fn main() {
fp.application('v ls')
fp.description('Installs, updates, and executes the V language server program')
fp.version('0.1')
fp.version('0.1.1')
// just to make sure whenever user wants to
// interact directly with the executable

View File

@ -324,8 +324,8 @@ fn vpm_install_from_vcs(module_names []string, vcs_key string) {
eprintln('Removing module "${minfo.final_module_path}" ...')
os.rmdir_all(minfo.final_module_path) or {
errors++
println('Errors while removing "${minfo.final_module_path}" :')
println(err)
eprintln('Errors while removing "${minfo.final_module_path}" :')
eprintln(err)
continue
}
}
@ -342,6 +342,14 @@ fn vpm_install_from_vcs(module_names []string, vcs_key string) {
continue
}
println('Module "${name}" relocated to "${vmod_.name}" successfully.')
publisher_dir := final_module_path.all_before_last(os.path_separator)
if os.is_dir_empty(publisher_dir) {
os.rmdir(publisher_dir) or {
errors++
eprintln('Errors while removing "${publisher_dir}" :')
eprintln(err)
}
}
final_module_path = minfo.final_module_path
}
name = vmod_.name

View File

@ -14,7 +14,7 @@ fn main() {
if os.is_dir(freetype_folder) {
println('Thirdparty "freetype" is already installed.')
} else {
s := os.execute('git clone --depth=1 ${freetype_repo_url} ${freetype_folder}')
s := os.execute('git clone --filter=blob:none ${freetype_repo_url} ${freetype_folder}')
if s.exit_code != 0 {
panic(s.output)
}

View File

@ -142,25 +142,28 @@ fn get_all_commands() []Command {
line: '${vexe} run examples/v_script.vsh > /dev/null'
okmsg: 'V can run the .VSH script file examples/v_script.vsh'
}
// Note: -experimental is used here, just to suppress the warningss,
// that are otherwise printed by the native backend,
// until globals and hash statements *are implemented*:
$if linux {
res << Command{
line: '${vexe} -b native run examples/native/hello_world.v > /dev/null'
line: '${vexe} -experimental -b native run examples/native/hello_world.v > /dev/null'
okmsg: 'V compiles and runs examples/native/hello_world.v on the native backend for linux'
}
}
// only compilation:
res << Command{
line: '${vexe} -os linux -b native -o hw.linux examples/hello_world.v'
line: '${vexe} -os linux -experimental -b native -o hw.linux examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the native backend for linux'
rmfile: 'hw.linux'
}
res << Command{
line: '${vexe} -os macos -b native -o hw.macos examples/hello_world.v'
line: '${vexe} -os macos -experimental -b native -o hw.macos examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the native backend for macos'
rmfile: 'hw.macos'
}
res << Command{
line: '${vexe} -os windows -b native -o hw.exe examples/hello_world.v'
line: '${vexe} -os windows -experimental -b native -o hw.exe examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the native backend for windows'
rmfile: 'hw.exe'
}

View File

@ -22,7 +22,6 @@ const vet_known_failing_windows = [
]
const vet_folders = [
'vlib/sqlite',
'vlib/v',
'vlib/x/json2',
'vlib/x/ttf',
@ -53,7 +52,7 @@ const is_fix = '-fix' in os.args
fn main() {
args_string := os.args[1..].join(' ')
pass_args := args_string.all_before('test-cleancode')
v_test_vetting(pass_args)
v_test_vetting(pass_args)!
}
fn tsession(vargs string, tool_source string, tool_cmd string, tool_args string, flist []string, slist []string) testing.TestSession {
@ -72,8 +71,8 @@ fn tsession(vargs string, tool_source string, tool_cmd string, tool_args string,
return test_session
}
fn v_test_vetting(vargs string) {
expanded_vet_list := util.find_all_v_files(vet_folders) or { return }
fn v_test_vetting(vargs string) ! {
expanded_vet_list := util.find_all_v_files(vet_folders)!
mut vet_known_exceptions := vet_known_failing.clone()
if os.user_os() == 'windows' {
vet_known_exceptions << vet_known_failing_windows

View File

@ -326,9 +326,11 @@ fn main() {
tsession.skip_files << test_js_files.map(it.replace(testroot, ''))
}
testing.find_started_process('mysqld') or {
tsession.skip_files << 'vlib/mysql/mysql_orm_test.v'
tsession.skip_files << 'vlib/db/mysql/mysql_orm_test.v'
}
testing.find_started_process('postgres') or {
tsession.skip_files << 'vlib/db/pg/pg_orm_test.v'
}
testing.find_started_process('postgres') or { tsession.skip_files << 'vlib/pg/pg_orm_test.v' }
if github_job == 'windows-tcc' {
// TODO: fix these ASAP

View File

@ -0,0 +1,7 @@
A simple example to show how to call a function written in v from ruby
Step 1: Compile the v code to a shared library using `v -d no_backtrace -shared test.v`
Step 2: Run the ruby file using `ruby test.rb`
Note: you do not need `-d no_backtrace` if you use gcc or clang .

View File

@ -0,0 +1,23 @@
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'ffi'
end
require 'ffi'
module Lib
extend FFI::Library
ffi_lib File.join(File.dirname(__FILE__), 'test.so')
attach_function :square, [:int], :int
attach_function :sqrt_of_sum_of_squares, [:double, :double], :double
end
puts "Lib.square(10) result is #{Lib.square(10)}"
raise 'Cannot validate V square().' unless Lib.square(10) == 100
raise 'Cannot validate V sqrt_of_sum_of_squares().' unless \
Lib.sqrt_of_sum_of_squares(1.1, 2.2) == Math.sqrt(1.1*1.1 + 2.2*2.2)

View File

@ -0,0 +1,13 @@
module test
import math
[export: 'square']
fn square(i int) int {
return i * i
}
[export: 'sqrt_of_sum_of_squares']
fn sqrt_of_sum_of_squares(x f64, y f64) f64 {
return math.sqrt(x * x + y * y)
}

View File

@ -74,12 +74,12 @@ fn on_frame(mut app App) {
// draw minute hand
mut j := f32(n.minute)
if n.second == 59 { // make minute hand move smoothly
j += f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0))
j += f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0))
}
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.minute_hand, hand_color, j * 6)
// draw second hand with smooth transition
k := f32(n.second) + f32(math.sin(f32(n.microsecond) / 1e6 * math.pi / 2.0))
k := f32(n.second) + f32(math.sin(f32(n.nanosecond) / 1e9 * math.pi / 2.0))
draw_convex_poly_rotate(mut app.gg, app.dpi_scale, app.second_hand, second_hand_color,
0 + k * 6)

View File

@ -2,20 +2,19 @@ import db.sqlite
fn main() {
db := sqlite.connect(':memory:')!
db.exec("create table users (id integer primary key, name text default '');")
db.exec("create table users (id integer primary key, name text default '');") or { panic(err) }
db.exec("insert into users (name) values ('Sam')")
db.exec("insert into users (name) values ('Peter')")
db.exec("insert into users (name) values ('Kate')")
db.exec("insert into users (name) values ('Sam')")!
db.exec("insert into users (name) values ('Peter')")!
db.exec("insert into users (name) values ('Kate')")!
nr_users := db.q_int('select count(*) from users')
nr_users := db.q_int('select count(*) from users')!
println('nr users = ${nr_users}')
name := db.q_string('select name from users where id = 1')
name := db.q_string('select name from users where id = 1')!
assert name == 'Sam'
users, code := db.exec('select * from users')
println('SQL Result code: ${code}')
users := db.exec('select * from users')!
for row in users {
println(row.vals)
}

View File

@ -44,8 +44,8 @@ pub fn (mut app App) index() vweb.Result {
fn (mut app App) update_db() !int {
mut db := mydb()!
db.exec('INSERT INTO visits (created_at) VALUES ("")')
visits := db.q_int('SELECT count(*) FROM visits')
db.exec('INSERT INTO visits (created_at) VALUES ("")')!
visits := db.q_int('SELECT count(*) FROM visits')!
db.close()!
return visits
}
@ -56,10 +56,10 @@ fn main() {
println('`v -d vweb_livereload watch --keep run examples/vwatch/web_server/`')
println('')
mut db := mydb()!
db.exec('CREATE TABLE visits (id integer primary key AUTOINCREMENT, created_at timestamp default current_timestamp);')
db.exec('CREATE TABLE visits (id integer primary key AUTOINCREMENT, created_at timestamp default current_timestamp);')!
db.exec('CREATE TRIGGER INSERT_visits AFTER INSERT ON visits BEGIN
UPDATE visits SET created_at = datetime("now", "localtime") WHERE rowid = new.rowid ;
END')
END')!
db.close()!
vweb.run(&App{}, 19123)
}

View File

@ -40,7 +40,7 @@ Now V should be globally available on your system.
### Install SQLite development dependency
If you don't have it already installed, look at the
[`sqlite` README](../../vlib/sqlite/README.md) for instructions.
[`sqlite` README](../../vlib/db/sqlite/README.md) for instructions.
### Creating a new Vweb project

View File

@ -286,8 +286,7 @@ pub fn winapi_lasterr_str() string {
}
mut msgbuf := &u16(0)
res := C.FormatMessage(C.FORMAT_MESSAGE_ALLOCATE_BUFFER | C.FORMAT_MESSAGE_FROM_SYSTEM | C.FORMAT_MESSAGE_IGNORE_INSERTS,
C.NULL, err_msg_id, C.MAKELANGID(C.LANG_NEUTRAL, C.SUBLANG_DEFAULT), &msgbuf,
0, C.NULL)
0, err_msg_id, 0, voidptr(&msgbuf), 0, 0)
err_msg := if res == 0 {
'Win-API error ${err_msg_id}'
} else {

View File

@ -349,7 +349,7 @@ fn C.FindClose(hFindFile voidptr)
// macro
fn C.MAKELANGID(lgid voidptr, srtid voidptr) int
fn C.FormatMessage(dwFlags u32, lpSource voidptr, dwMessageId u32, dwLanguageId u32, lpBuffer voidptr, nSize int, arguments ...voidptr) voidptr
fn C.FormatMessage(dwFlags u32, lpSource voidptr, dwMessageId u32, dwLanguageId u32, lpBuffer voidptr, nSize u32, arguments ...voidptr) u32
fn C.CloseHandle(voidptr) int

View File

@ -1,6 +1,7 @@
module mysql
pub struct Result {
pub:
result &C.MYSQL_RES = unsafe { nil }
}

View File

@ -34,6 +34,6 @@ For instance:
import db.sqlite
db := sqlite.connect('foo.db') or { panic(err) }
db.synchronization_mode(sqlite.SyncMode.off)
db.journal_mode(sqlite.JournalMode.memory)
db.synchronization_mode(sqlite.SyncMode.off)!
db.journal_mode(sqlite.JournalMode.memory)!
```

View File

@ -75,7 +75,7 @@ pub fn (db DB) delete(table string, where orm.QueryData) ! {
pub fn (db DB) last_id() int {
query := 'SELECT last_insert_rowid();'
return db.q_int(query)
return db.q_int(query) or { 0 }
}
// DDL (table creation/destroying etc)

View File

@ -131,7 +131,7 @@ pub fn connect(path string) !DB {
code := C.sqlite3_open(&char(path.str), &db)
if code != 0 {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db))) }
code: code
}
}
@ -150,7 +150,7 @@ pub fn (mut db DB) close() !bool {
db.is_open = false
} else {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) }
code: code
}
}
@ -180,41 +180,50 @@ pub fn (db &DB) get_affected_rows_count() int {
return C.sqlite3_changes(db.conn)
}
// q_int returns a single integer value, from the first column of the result of executing `query`
pub fn (db &DB) q_int(query string) int {
// q_int returns a single integer value, from the first column of the result of executing `query`, or an error on failure
pub fn (db &DB) q_int(query string) !int {
stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
C.sqlite3_step(stmt)
code := C.sqlite3_step(stmt)
if code != sqlite.sqlite_row {
return db.error_message(code, query)
}
res := C.sqlite3_column_int(stmt, 0)
return res
}
// q_string returns a single string value, from the first column of the result of executing `query`
pub fn (db &DB) q_string(query string) string {
// q_string returns a single string value, from the first column of the result of executing `query`, or an error on failure
pub fn (db &DB) q_string(query string) !string {
stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
C.sqlite3_step(stmt)
code := C.sqlite3_step(stmt)
if code != sqlite.sqlite_row {
return db.error_message(code, query)
}
val := unsafe { &u8(C.sqlite3_column_text(stmt, 0)) }
return if val != &u8(0) { unsafe { tos_clone(val) } } else { '' }
}
// exec executes the query on the given `db`, and returns an array of all the results, alongside any result code.
// Result codes: https://www.sqlite.org/rescode.html
// exec executes the query on the given `db`, and returns an array of all the results, or an error on failure
[manualfree]
pub fn (db &DB) exec(query string) ([]Row, int) {
pub fn (db &DB) exec(query string) ![]Row {
stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if code != sqlite.sqlite_ok {
return db.error_message(code, query)
}
nr_cols := C.sqlite3_column_count(stmt)
mut res := 0
mut rows := []Row{}
@ -236,26 +245,21 @@ pub fn (db &DB) exec(query string) ([]Row, int) {
}
rows << row
}
return rows, res
return rows
}
// exec_one executes a query on the given `db`.
// It returns either the first row from the result, if the query was successful, or an error.
[manualfree]
pub fn (db &DB) exec_one(query string) !Row {
rows, code := db.exec(query)
rows := db.exec(query)!
defer {
unsafe { rows.free() }
}
if rows.len == 0 {
return &SQLError{
msg: 'No rows'
code: code
}
} else if code != 101 {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
code: code
code: sqlite.sqlite_done
}
}
res := rows[0]
@ -285,13 +289,60 @@ pub fn (db &DB) exec_none(query string) int {
return code
}
// TODO pub fn (db &DB) exec_param(query string, param string) []Row {
// exec_param_many executes a query with parameters provided as ?,
// and returns either an error on failure, or the full result set on success
pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
mut stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), -1, &stmt, 0)
if code != 0 {
return db.error_message(code, query)
}
for i, param in params {
code = C.sqlite3_bind_text(stmt, i + 1, voidptr(param.str), param.len, 0)
if code != 0 {
return db.error_message(code, query)
}
}
nr_cols := C.sqlite3_column_count(stmt)
mut res := 0
mut rows := []Row{}
for {
res = C.sqlite3_step(stmt)
if res != sqlite.sqlite_row {
break
}
mut row := Row{}
for i in 0 .. nr_cols {
val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) }
if val == &u8(0) {
row.vals << ''
} else {
row.vals << unsafe { tos_clone(val) }
}
}
rows << row
}
return rows
}
// exec_param executes a query with one parameter provided as a ?,
// and returns either an error on failure, or the full result set on success
pub fn (db &DB) exec_param(query string, param string) ![]Row {
return db.exec_param_many(query, [param])
}
// create_table issues a "create table if not exists" command to the db.
// It creates the table named 'table_name', with columns generated from 'columns' array.
// The default columns type will be TEXT.
pub fn (db &DB) create_table(table_name string, columns []string) {
db.exec('create table if not exists ${table_name} (' + columns.join(',\n') + ')')
pub fn (db &DB) create_table(table_name string, columns []string) ! {
db.exec('create table if not exists ${table_name} (' + columns.join(',\n') + ')')!
}
// busy_timeout sets a busy timeout in milliseconds.
@ -304,37 +355,39 @@ pub fn (db &DB) busy_timeout(ms int) int {
// synchronization_mode sets disk synchronization mode, which controls how
// aggressively SQLite will write data to physical storage.
// If the command fails to execute an error is returned
// .off: No syncs at all. (fastest)
// .normal: Sync after each sequence of critical disk operations.
// .full: Sync after each critical disk operation (slowest).
pub fn (db &DB) synchronization_mode(sync_mode SyncMode) {
pub fn (db &DB) synchronization_mode(sync_mode SyncMode) ! {
if sync_mode == .off {
db.exec('pragma synchronous = OFF;')
db.exec('pragma synchronous = OFF;')!
} else if sync_mode == .full {
db.exec('pragma synchronous = FULL;')
db.exec('pragma synchronous = FULL;')!
} else {
db.exec('pragma synchronous = NORMAL;')
db.exec('pragma synchronous = NORMAL;')!
}
}
// journal_mode controls how the journal file is stored and processed.
// If the command fails to execute an error is returned
// .off: No journal record is kept. (fastest)
// .memory: Journal record is held in memory, rather than on disk.
// .delete: At the conclusion of a transaction, journal file is deleted.
// .truncate: Journal file is truncated to a length of zero bytes.
// .persist: Journal file is left in place, but the header is overwritten to indicate journal is no longer valid.
pub fn (db &DB) journal_mode(journal_mode JournalMode) {
pub fn (db &DB) journal_mode(journal_mode JournalMode) ! {
if journal_mode == .off {
db.exec('pragma journal_mode = OFF;')
db.exec('pragma journal_mode = OFF;')!
} else if journal_mode == .delete {
db.exec('pragma journal_mode = DELETE;')
db.exec('pragma journal_mode = DELETE;')!
} else if journal_mode == .truncate {
db.exec('pragma journal_mode = TRUNCATE;')
db.exec('pragma journal_mode = TRUNCATE;')!
} else if journal_mode == .persist {
db.exec('pragma journal_mode = PERSIST;')
db.exec('pragma journal_mode = PERSIST;')!
} else if journal_mode == .memory {
db.exec('pragma journal_mode = MEMORY;')
db.exec('pragma journal_mode = MEMORY;')!
} else {
db.exec('pragma journal_mode = MEMORY;')
db.exec('pragma journal_mode = MEMORY;')!
}
}

View File

@ -103,11 +103,10 @@ fn test_sqlite_orm() {
create table TestCustomSqlType
}!
mut result_custom_sql, mut exec_custom_code := db.exec('
mut result_custom_sql := db.exec('
pragma table_info(TestCustomSqlType);
')
')!
assert exec_custom_code == 101
mut table_info_types_results := []string{}
information_schema_custom_sql := ['INTEGER', 'INTEGER', 'TEXT', 'REAL', 'NUMERIC', 'TEXT',
'INTEGER', 'INTEGER']
@ -128,11 +127,10 @@ fn test_sqlite_orm() {
create table TestDefaultAtribute
}!
mut result_default_sql, mut code := db.exec('
mut result_default_sql := db.exec('
pragma table_info(TestDefaultAtribute);
')
')!
assert code == 101
mut information_schema_data_types_results := []string{}
information_schema_default_sql := ['', '', 'CURRENT_TIME', 'CURRENT_DATE', 'CURRENT_TIMESTAMP']
@ -176,7 +174,7 @@ fn test_get_affected_rows_count() {
db.exec('create table EntityToTest(
id integer not null constraint tbl_pk primary key,
smth integer
);')
);')!
fst := EntityToTest{
id: 1

View File

@ -35,43 +35,48 @@ fn test_sqlite() {
}
mut db := sqlite.connect(':memory:') or { panic(err) }
assert db.is_open
db.exec('drop table if exists users')
db.exec("create table users (id integer primary key, name text default '');")
db.exec("insert into users (name) values ('Sam')")
db.exec('drop table if exists users')!
db.exec("create table users (id integer primary key, name text default '');")!
db.exec("insert into users (name) values ('Sam')")!
assert db.last_insert_rowid() == 1
assert db.get_affected_rows_count() == 1
db.exec("insert into users (name) values ('Peter')")
db.exec("insert into users (name) values ('Peter')")!
assert db.last_insert_rowid() == 2
db.exec("insert into users (name) values ('Kate')")
db.exec("insert into users (name) values ('Kate')")!
assert db.last_insert_rowid() == 3
nr_users := db.q_int('select count(*) from users')
assert nr_users == 3
name := db.q_string('select name from users where id = 1')
db.exec_param('insert into users (name) values (?)', 'Tom')!
assert db.last_insert_rowid() == 4
nr_users := db.q_int('select count(*) from users')!
assert nr_users == 4
name := db.q_string('select name from users where id = 1')!
assert name == 'Sam'
username := db.exec_param('select name from users where id = ?', '1')!
assert username[0].vals[0] == 'Sam'
// this insert will be rejected due to duplicated id
db.exec("insert into users (id,name) values (1,'Sam')")
db.exec("insert into users (id,name) values (1,'Sam')")!
assert db.get_affected_rows_count() == 0
users, mut code := db.exec('select * from users')
assert users.len == 3
assert code == 101
code = db.exec_none('vacuum')
users := db.exec('select * from users')!
assert users.len == 4
code := db.exec_none('vacuum')
assert code == 101
user := db.exec_one('select * from users where id = 3') or { panic(err) }
println(user)
assert user.vals.len == 2
db.exec("update users set name='zzzz' where name='qqqq'")
db.exec("update users set name='zzzz' where name='qqqq'")!
assert db.get_affected_rows_count() == 0
db.exec("update users set name='Peter1' where name='Peter'")
db.exec("update users set name='Peter1' where name='Peter'")!
assert db.get_affected_rows_count() == 1
db.exec_param_many('update users set name=? where name=?', ['Peter', 'Peter1'])!
assert db.get_affected_rows_count() == 1
db.exec("delete from users where name='qqqq'")
db.exec("delete from users where name='qqqq'")!
assert db.get_affected_rows_count() == 0
db.exec("delete from users where name='Sam'")
db.exec("delete from users where name='Sam'")!
assert db.get_affected_rows_count() == 1
db.close() or { panic(err) }

View File

@ -207,13 +207,13 @@ fn overhead_for(c &Chunk) usize {
//
// Why not `interface?` Interfaces require memory allocation so it is simpler to pass a struct.
pub struct Allocator {
alloc fn (voidptr, usize) (voidptr, usize, u32)
remap fn (voidptr, voidptr, usize, usize, bool) voidptr
free_part fn (voidptr, voidptr, usize, usize) bool
free_ fn (voidptr, voidptr, usize) bool
can_release_part fn (voidptr, u32) bool
allocates_zeros fn (voidptr) bool
page_size fn (voidptr) usize // not a constant field because some platforms might have different page sizes depending on configs
alloc fn (voidptr, usize) (voidptr, usize, u32) = unsafe { nil }
remap fn (voidptr, voidptr, usize, usize, bool) voidptr = unsafe { nil }
free_part fn (voidptr, voidptr, usize, usize) bool = unsafe { nil }
free_ fn (voidptr, voidptr, usize) bool = unsafe { nil }
can_release_part fn (voidptr, u32) bool = unsafe { nil }
allocates_zeros fn (voidptr) bool = unsafe { nil }
page_size fn (voidptr) usize = unsafe { nil } // not a constant field because some platforms might have different page sizes depending on configs
data voidptr
}

View File

@ -32,17 +32,17 @@ const (
fn test_search_tag_by_type() {
mut dom := parse(html.html)
tag := dom.get_tag('body')[0]
assert tag.get_tag('div') or { assert false }.attributes['id'] == '1st'
assert tag.get_tag_by_attribute('href') or { assert false }.content == 'V'
tag := dom.get_tags(GetTagsOptions{'body'})[0]
assert tag.get_tag('div')?.attributes['id'] == '1st'
assert tag.get_tag_by_attribute('href')?.content == 'V'
// TODO: update after improved parsing to not add trailing white space to attribute values
assert tag.get_tag_by_attribute_value('id', '3rd') or { assert false }.str() == '<div id="3rd" ></div>'
assert tag.get_tag_by_class_name('foo') or { assert false }.attributes['class'] == 'foo bar'
assert tag.get_tag_by_attribute_value('id', '3rd')?.str() == '<div id="3rd" ></div>'
assert tag.get_tag_by_class_name('foo')?.attributes['class'] == 'foo bar'
}
fn test_search_tags_by_type() {
mut dom := parse(html.html)
tag := dom.get_tag_by_attribute_value('id', '2nd')[0]
tag := dom.get_tags_by_attribute_value('id', '2nd')[0]
assert tag.get_tags('div').len == 5
assert tag.get_tags_by_attribute('href')[2].content == 'vpm'
assert tag.get_tags_by_attribute_value('class', 'bar').len == 3
@ -65,7 +65,7 @@ fn generate_temp_html_with_classes() string {
fn test_search_by_class() {
mut dom := parse(generate_temp_html_with_classes())
tag := dom.get_tag('body')[0]
tag := dom.get_tags(GetTagsOptions{'body'})[0]
single_class_tags := tag.get_tags_by_class_name('single')
common_class_tags := tag.get_tags_by_class_name('common')
complex_class_tags := tag.get_tags_by_class_name('complex-0', 'complex-1', 'complex-2')

View File

@ -0,0 +1,56 @@
import os
import time
import context
import net.mbedtls
fn server() ! {
cfg := mbedtls.SSLConnectConfig{
cert: '-----BEGIN CERTIFICATE-----\nMIIEOTCCAyECFG64Q2g46jZb3kRbDOJWX/BwjSp6MA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwODAyMTcyOTQyWhgPMjA1MDEyMTcx\nNzI5NDJaMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYD\nVQQHDAtMb3MgQW5nZWxlczEdMBsGA1UECgwUQ2F0YWx5c3QgRGV2ZWxvcG1lbnQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBALqAI4fqUi+QBVWcsXglouLdOML5+w0+1hSR1KdO0Q5XPdQAs/yYWJ+KUkDw\nG++rfy9DUPq7FNRBVurXQkcAtn6gXdllGUSjwUiDo/N4mMOyS/2sufBuaeww7jVi\nrppH+zwP1tUnjRd6khl6bi1Ian9VSzr3Iy9CkXIg1GU4CPXkOydLeoQfepXxWoK1\nOUNwT3VKC/stAfY3j/NIIeiJYkyuRGFCkxn/BUjN+AsXiTugRcYKEFHdIPkOuCXp\nYbhf+lLsczpxCs3rdZG9b/N6mEDCzXTmeHkmsjdPTf+1k5DZZvKzVBBrgdxCgBb7\n5RwjF5v9WmnIc33wWgfJC6FaUzj9NYxYUbPHD+jTz0rJB/jj4u/xJlM/e5NRmXdW\n70pOMKXtWjRSolLOFIPKLY1qs3KMTAZxKKWPDDF7WlMJxMRt7nnnks5yw43Nog4C\njDLk1ZgETnPpLgo3jbmJdIv+OHKTJrBlVvDq7VTyixCoS5G8KoOmyQJhaXG6NwE2\niVhH5JIKgzgCfetfDsnjxqJ/qtrFXPa8FF2TsomD0NK/GZmIcs+9OeVB75Jn5uhF\nfLHScpiTbuu5w3P/LI/MqihLRB6RRNnRzPH8fIg5bYC9b770ta/8GcFRuYE8t+UR\nGtqXJoIKixbDlqV54kal8FQzYzhETf9+NM6Kb/lKEfG/pslvAgMBAAEwDQYJKoZI\nhvcNAQELBQADggEBALI3uNiNO0QE1brA3QYFK+d9ZroB72NrJ0UNkzYHDg2Fc6xg\n4aVVfaxY08+TmKc0JlMOW+pUxeCW/+UBSngdQiR9EE9xm0k0XIrAsy9RXxRvEtPu\nM1VI2h7ayp1Y2BrnQinevTSgtqLRyS1VbOFRl1FiyVvinw2I0KsDdAMNevAPXcOa\nQ8pUgUq6f56DkhocQaj+hxD/uV8HryNxuoSXnPhvfTN3z4YRGzsaWevJ9EYJliOM\n+XugcqfFJ+W7/QCEcAHCL+Bw6OydG5NFORr3p57PXjjcL/uKmxPBrWg2Bz6uT4uR\nMhj0zttiFHLAt9jGfyk6W57UNUja1e1ggftJJhs=\n-----END CERTIFICATE-----\n'
cert_key: '-----BEGIN RSA PRIVATE KEY-----\nMIIJKQIBAAKCAgEAuoAjh+pSL5AFVZyxeCWi4t04wvn7DT7WFJHUp07RDlc91ACz\n/JhYn4pSQPAb76t/L0NQ+rsU1EFW6tdCRwC2fqBd2WUZRKPBSIOj83iYw7JL/ay5\n8G5p7DDuNWKumkf7PA/W1SeNF3qSGXpuLUhqf1VLOvcjL0KRciDUZTgI9eQ7J0t6\nhB96lfFagrU5Q3BPdUoL+y0B9jeP80gh6IliTK5EYUKTGf8FSM34CxeJO6BFxgoQ\nUd0g+Q64JelhuF/6UuxzOnEKzet1kb1v83qYQMLNdOZ4eSayN09N/7WTkNlm8rNU\nEGuB3EKAFvvlHCMXm/1aachzffBaB8kLoVpTOP01jFhRs8cP6NPPSskH+OPi7/Em\nUz97k1GZd1bvSk4wpe1aNFKiUs4Ug8otjWqzcoxMBnEopY8MMXtaUwnExG3ueeeS\nznLDjc2iDgKMMuTVmAROc+kuCjeNuYl0i/44cpMmsGVW8OrtVPKLEKhLkbwqg6bJ\nAmFpcbo3ATaJWEfkkgqDOAJ9618OyePGon+q2sVc9rwUXZOyiYPQ0r8ZmYhyz705\n5UHvkmfm6EV8sdJymJNu67nDc/8sj8yqKEtEHpFE2dHM8fx8iDltgL1vvvS1r/wZ\nwVG5gTy35REa2pcmggqLFsOWpXniRqXwVDNjOERN/340zopv+UoR8b+myW8CAwEA\nAQKCAgEAkcoffF0JOBMOiHlAJhrNtSiX+ZruzNDlCxlgshUjyWEbfQG7sWbqSHUZ\njZflTrqyZqDpyca7Jp2ZM2Vocxa0klIMayfj08trCaOWY3pPeROE4d3HUJMPjEpH\nvEXTFdnVJIOBPgl3+vWfBfm17QIh9j4X3BVbVNNl3WCaiDGAl699Kl+Pe38cFeCh\nD3JZPEWsZ5SlvwjU8sNGbThjAWN8C1NjMuCXG4hGej5Ae3M/nPPR91jgnw4Me4Ut\nIL3K3RVyGqaqAPJjLsu0kWQUArJAGMfvUkXjwVklkaUV5SHtJBs+pdTXjyprTmJR\nvSXWWON5zkAEEJNY7QcZaeKYi96PFLUFI+ciEdnXn74CfSKhgZCBo+OyFZjDWW5R\nNmgAbZTN2RW0z+V54Lg36JfJrmiGs8TN06KwNjFo+iOJCdQnoUSIhTlmMfVbXPah\ntRfQvwqtfqVS9W/jkiGq9yDDqyXx093R/QTM/XqDlWJ2iOJFppOJefGFCWF6Fwll\nVT9povTAGQmXFiAxwFZxWtbFa0i8fP5QG80X6l/gRklSd6ZXAVvcLkaFGqxunDAe\nrYC2jBwHWRpVmbxw880SWRzlAsJXc7M8PQnBTlyX1mFZNnwAJgqplz0BQHQhQh4V\nqNfisUm9smtda+Hr9GBBUxs09ulery3I0lQjsArVxPqPVgUbFPECggEBANqLA5fH\n2LupOBoFH/fK5jixyGdSB8eJvU+XuS8RBBexnzTQApmDHiU7Axa/cKvxAfUgwBpU\n6OIsL6Lq6wowVInBgo7GraACwspGMIP8Z7+A8qDgSWIcpXP21Ny2RW+nukdH8ZnV\nTFtiFxLYU9GRfzSUcqvE0miKfMGP/S9Cqbew00K6CQ2xurLTR2AchfUQZJJIg7eF\nRBoftthXLQ+s1JoiLJX2gqCliFy32RMAUP+pKvKVJmVQh8bxEkoEzTV2eY7eTxsH\nJDH5hD66EZ5bW/nVAMruJ3iKjy3WvjDbnddNAz9IFKrd1RMP9dgSEKuSv/HhqwPe\n1q9Wm6LWZo8BlYcCggEBANp3M14QMcMxRlZE0TiSopi1CaE8OG0C9apToS1dol2s\n4lCsWHVPIC516LMPGU0bmCdtwJey1mgXQEKVxCWHkVhhoCKT/tN53o5qkptrhrXL\npbqmRfoMXI7LwJU+Vqi5fwSPGrSR/IzHwCUL7pHTbYN7wT5rr2rcC84XYSX31TFm\nNfMnbDuUk33ycAo07Vqts5A5FN+xViEUMFSDmfA2XmOAV77awz0l/3n3qOg9lQYe\nU4Av2nT19lGELirLInkB1ndLirWAcLaCBXKOLW4bzpNm9Bt8aiziVzcUzlJlLa+1\nnb/7//xzKi0eM/BhyJfhsmOz5B8AQ6Ca/keDk8M7JtkCggEARl8DDinE6VCpBv/l\ndlX4YgMlQ9fPN3pr4ig58iTpi3Ofj1L3s1TcLSLecMG+Vy9o8PTVxuTWhJWz1SMO\nAh7j6ePM1Yq2N9MLxDRrxOROyASOnCz8lEIjKL8vdc6fdz+sJO3OpzleuAJS6beM\n7euK6XRvpE3hbtZBK9bgsQonOkYPEOp0pds4AgM0dYdZvzrDF7OP7lVUQ5E4wFr5\n4JVHdEZS0wsoru/+g9STaqHscxaXBLvwPCl9Pxs7R2haZ7+5jr6Y/FwFVK5C3ivu\nJm7GpCDpe27KeO8tAZancXYWUlCzHfpo5Ug/Jz85a5UNlyHO+uUuuzVTLeyWew3M\nwnnBGwKCAQEAqGTBP3wUH3TX1p9s9cJxemvxZEra44woeIXF8wX9pV8hgzWVabb4\nA1f3ai31Pq5KdfnvPf8nrUxex/RRIOyCaDG4EW8qOS/zEKutHgef6nly4ZBQ2BC3\nN4pug5ttiNiSw5za5NyyYoGF5ghweA8UlwjJR6gRqri6kL0MsQt7VXyHkUmN787y\ncV5yZiut2PuTMVQOdu5miVDagAqAmdwOnXvMJtzRKU0kw4rWs0zklbbCfkhkh0sf\n9m2AeJPjmoqEGags3wKF3ugR8t8MvZbJgG0XNCiOXtKIj3iGIJTExm+jjNxd0OWk\nWOqy9lMpH4lky91ZtVuqxR0za0RMnWv24QKCAQBe8l0w9AYVNGDLv1jyPcbsncty\nNYI81yqe2mL+TC00sMCeil7C7WCP7kRklY01rH5q5gJ9Q1UV+bOj2fQdXDmQ5Bgo\n41jseh44gkbuXAeWcSDrDkJCrfvlNqFobTmUb8cdb9aQlHYfOJ31367LJspiw2SY\nmCbnLQ5sMnyBiMkcn0GfBV6IAkZVN73DPa8a1m/0Qrrv1GmBJFVbuZd9d/hAWpHa\nekhXPq0Sta+RNDfBR3aI5lAmVA17qRGiubQYJ+Ldq0aRJ40fGE51ctoSU/5RMcmh\n6+Qro+jSC94L46xMFp+1J5atgB1p/jVzTT/Ws7SLyotYUSL8zU7tcLiycQXs\n-----END RSA PRIVATE KEY-----\n'
validate: false
in_memory_verification: true
}
mut srv := mbedtls.new_ssl_listener('127.0.0.1:64443', cfg) or {
eprintln('Listen: ${err}')
return err
}
eprintln('[+] Listening')
mut cli := srv.accept() or {
eprintln('Accept: ${err}')
return err
}
eprintln('[+] Accepted connection')
cli.shutdown()!
}
fn read_input() string {
return os.input_opt('Message: ') or { 'Empty' }
}
[if network ?]
fn test_shutdown_does_not_panic() {
_ := spawn server()
time.sleep(1 * time.second)
mut client := mbedtls.new_ssl_conn(validate: false)!
client.dial('127.0.0.1', 64443) or {
eprintln('Connect: ${err}')
return
}
eprintln('[+] Connected')
time.sleep(1 * time.second)
mut background := context.background()
mut ctx, cancel := context.with_timeout(mut background, 2 * time.second)
spawn read_input()
mut done := ctx.done()
for {
select {
_ := <-done {
break
}
}
}
eprintln('Timeout without panic - OK')
assert true
}

View File

@ -115,15 +115,17 @@ fn (mut l SSLListener) init() ! {
if l.config.in_memory_verification {
if l.config.verify != '' {
ret = C.mbedtls_x509_crt_parse(&l.certs.cacert, l.config.verify.str, l.config.verify.len)
ret = C.mbedtls_x509_crt_parse(&l.certs.cacert, l.config.verify.str,
l.config.verify.len + 1)
}
if l.config.cert != '' {
ret = C.mbedtls_x509_crt_parse(&l.certs.client_cert, l.config.cert.str, l.config.cert.len)
ret = C.mbedtls_x509_crt_parse(&l.certs.client_cert, l.config.cert.str,
l.config.cert.len + 1)
}
if l.config.cert_key != '' {
unsafe {
ret = C.mbedtls_pk_parse_key(&l.certs.client_key, l.config.cert_key.str,
l.config.cert_key.len, 0, 0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
l.config.cert_key.len + 1, 0, 0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
}
}
} else {
@ -176,10 +178,9 @@ fn (mut l SSLListener) init() ! {
// accepts a new connection and returns a SSLConn of the connected client
pub fn (mut l SSLListener) accept() !&SSLConn {
mut conn := &SSLConn{
conf: l.conf
config: l.config
opened: true
owns_socket: true
owns_socket: false
}
// TODO: save the client's IP address somewhere (maybe add a field to SSLConn ?)
@ -290,15 +291,17 @@ fn (mut s SSLConn) init() ! {
if s.config.in_memory_verification {
if s.config.verify != '' {
ret = C.mbedtls_x509_crt_parse(&s.certs.cacert, s.config.verify.str, s.config.verify.len)
ret = C.mbedtls_x509_crt_parse(&s.certs.cacert, s.config.verify.str,
s.config.verify.len + 1)
}
if s.config.cert != '' {
ret = C.mbedtls_x509_crt_parse(&s.certs.client_cert, s.config.cert.str, s.config.cert.len)
ret = C.mbedtls_x509_crt_parse(&s.certs.client_cert, s.config.cert.str,
s.config.cert.len + 1)
}
if s.config.cert_key != '' {
unsafe {
ret = C.mbedtls_pk_parse_key(&s.certs.client_key, s.config.cert_key.str,
s.config.cert_key.len, 0, 0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
s.config.cert_key.len + 1, 0, 0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
}
}
} else {

View File

@ -23,7 +23,7 @@ fn test_ensure_db_exists_and_user_table_is_ok() {
assert true
eprintln('> drop pre-existing User table...')
db.exec('drop table if exists User')
db.exec('drop table if exists User')!
eprintln('> creating User table...')
sql db {

View File

@ -0,0 +1,228 @@
module notify
import time
import os
#insert "@VEXEROOT/vlib/os/notify/kqueue.h"
struct C.kevent {
mut:
ident u32
filter i16
flags u16
fflags u32
data int
udata voidptr
}
fn C.kqueue() int
fn C.__kevent__(int, voidptr, int, voidptr, int, voidptr) int
fn C.EV_SET(voidptr, u32, i16, u16, u32, int, voidptr)
// KqueueNotifier provides methods that implement FdNotifier using the
// kqueue I/O event notification facility (macos, freeBSD, xxxBSD...unix only)
struct KqueueNotifier {
kqueue_fd int
}
// KqueueEvent describes an event that occurred for a file descriptor in
// the watch list
struct KqueueEvent {
pub:
fd int
kind FdEventType
}
// new creates a new KqueueNotifier
// The FdNotifier interface is returned to allow OS specific
// implementations without exposing the concrete type
pub fn new() !FdNotifier {
fd := C.kqueue()
if fd == -1 {
return error(os.posix_get_error_msg(C.errno))
}
// Needed to circumvent V limitations
x := &KqueueNotifier{
kqueue_fd: fd
}
return x
}
const (
// filter types
kqueue_read = i16(C.EVFILT_READ)
kqueue_write = i16(C.EVFILT_WRITE)
kqueue_aio = i16(C.EVFILT_AIO)
kqueue_vnode = i16(C.EVFILT_VNODE)
kqueue_proc = i16(C.EVFILT_PROC)
kqueue_signal = i16(C.EVFILT_SIGNAL)
kqueue_timer = i16(C.EVFILT_TIMER)
kqueue_machport = i16(C.EVFILT_MACHPORT)
kqueue_fs = i16(C.EVFILT_FS)
kqueue_user = i16(C.EVFILT_USER)
kqueue_vm = i16(C.EVFILT_VM)
kqueue_exception = i16(C.EVFILT_EXCEPT)
kqueue_syscount = i16(C.EVFILT_SYSCOUNT)
// actions
kqueue_add = u16(C.EV_ADD)
kqueue_delete = u16(C.EV_DELETE)
kqueue_enable = u16(C.EV_ENABLE)
kqueue_disable = u16(C.EV_DISABLE)
// flags
kqueue_oneshot = u16(C.EV_ONESHOT)
kqueue_edge_trigger = u16(C.EV_CLEAR) // kqueue_clear
kqueue_receipt = u16(C.EV_RECEIPT)
kqueue_dispatch = u16(C.EV_DISPATCH)
kqueue_udata_specific = u16(C.EV_UDATA_SPECIFIC)
kqueue_dispatch2 = u16(C.EV_DISPATCH | C.EV_UDATA_SPECIFIC)
kqueue_vanished = u16(C.EV_VANISHED)
kqueue_sysflags = u16(C.EV_SYSFLAGS)
kqueue_flag0 = u16(C.EV_FLAG0)
kqueue_flag1 = u16(C.EV_FLAG1)
// returned values
kqueue_eof = u16(C.EV_EOF)
kqueue_error = u16(C.EV_ERROR)
)
// ctl is a helper method for add, modify, and remove
fn (mut kn KqueueNotifier) ctl(fd int, filter i16, flags u16) ! {
event := [1]C.kevent{}
C.EV_SET(&event[0], fd, filter, flags, 0, 0, unsafe { nil })
if C.__kevent__(kn.kqueue_fd, &event[0], 1, unsafe { nil }, 0, unsafe { nil }) == -1 {
return error(os.posix_get_error_msg(C.errno))
}
}
// add adds a file descriptor to the watch list
fn (mut kn KqueueNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ! {
filter := filter_to_mask(events)
flags := flags_to_mask(...conf)
kn.ctl(fd, filter, flags)!
}
// modify sets an existing entry in the watch list to the provided events and configuration
fn (mut kn KqueueNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ! {
kn.add(fd, events, ...conf)!
}
// remove removes a file descriptor from the watch list
fn (mut kn KqueueNotifier) remove(fd int) ! {
filter := notify.kqueue_read | notify.kqueue_write | notify.kqueue_exception
flags := notify.kqueue_delete
kn.ctl(fd, filter, flags)!
}
// wait waits to be notified of events on the watch list,
// returns at most 512 events
fn (mut kn KqueueNotifier) wait(timeout time.Duration) []FdEvent {
// arbitrary 512 limit; events will round robin on successive
// waits if the number exceeds this
// NOTE: we use a fixed size array here for stack allocation; this has
// the added bonus of making KqueueNotifier thread safe
events := [512]C.kevent{}
// populate events with the new events
to := &C.timespec{0, timeout.nanoseconds()}
count := C.__kevent__(kn.kqueue_fd, unsafe { nil }, 0, &events[0], events.len, to)
if count > 0 {
mut arr := []FdEvent{cap: count}
for i := 0; i < count; i++ {
fd := int(events[i].ident)
kind := event_mask_to_flag(events[i].filter, events[i].flags)
if kind.is_empty() {
// NOTE: tcc only reports the first event for some
// reason, leaving subsequent structs in the array as 0
// (or possibly garbage)
panic('encountered an empty event kind; this is most likely due to using tcc')
}
arr << &KqueueEvent{
fd: fd
kind: kind
}
}
return arr
}
return []
}
// close closes the KqueueNotifier,
// any successive calls to add, modify, remove, and wait should fail
fn (mut kn KqueueNotifier) close() ! {
if C.close(kn.kqueue_fd) == -1 {
return error(os.posix_get_error_msg(C.errno))
}
}
// event_mask_to_flag is a helper function that converts a bitmask
// returned by kevent() wait to FdEventType
fn event_mask_to_flag(filter i16, flags u16) FdEventType {
mut res := FdEventType.read
if filter & notify.kqueue_read != 0 {
res.set(.read)
}
if filter & notify.kqueue_write != 0 {
res.set(.write)
}
if filter & notify.kqueue_exception != 0 {
res.set(.exception)
}
if flags & notify.kqueue_eof != 0 {
res.set(.hangup)
}
if flags & notify.kqueue_error != 0 {
res.set(.error)
}
return res
}
// filter_to_mask is a helper function that converts FdEventType
// to a bitmask used by the C functions
fn filter_to_mask(events FdEventType) i16 {
mut mask := i16(0)
if events.has(.read) {
mask |= notify.kqueue_read
}
if events.has(.write) {
mask |= notify.kqueue_write
}
if events.has(.exception) {
mask |= notify.kqueue_exception
}
if events.has(.peer_hangup) {
panic("Kqueue does not support 'peer_hangup' event type.")
}
if events.has(.error) {
panic("Kqueue does not support 'error' event type.")
}
if events.has(.hangup) {
panic("Kqueue does not support 'hangup' event type.")
}
return mask
}
// flags_to_mask is a helper function that converts FdConfigFlags
// to a bitmask used by the C functions
fn flags_to_mask(confs ...FdConfigFlags) u16 {
mut mask := notify.kqueue_add | notify.kqueue_enable
for conf in confs {
if conf.has(.edge_trigger) {
mask |= notify.kqueue_edge_trigger
}
if conf.has(.one_shot) {
mask |= notify.kqueue_oneshot
}
if conf.has(.wake_up) {
panic("Kqueue does not support 'wake_up' flag.")
}
if conf.has(.exclusive) {
panic("Kqueue does not support 'exclusive' flag.")
}
}
return mask
}

View File

@ -26,14 +26,12 @@ fn C.epoll_wait(int, &C.epoll_event, int, int) int
// EpollNotifier provides methods that implement FdNotifier using the
// epoll I/O event notification facility (linux only)
[noinit]
struct EpollNotifier {
epoll_fd int
}
// EpollEvent describes an event that occurred for a file descriptor in
// the watch list
[noinit]
struct EpollEvent {
pub:
fd int

12
vlib/os/notify/kqueue.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef __KQUEUE_H
#define __KQUEUE_H
#include <sys/event.h>
// Due to the renaming of 'struct kevent' and function 'kevent',
// they are wrapped here to avoid conflicts.
int __kevent__(int handle, const struct kevent* changelist, int nchanges, struct kevent* eventlist, int nevents, const struct timespec* timeout) {
return kevent(handle, changelist, nchanges, eventlist, nevents, timeout);
}
#endif

View File

@ -5,7 +5,7 @@ import os.notify
// make a pipe and return the (read, write) file descriptors
fn make_pipe() !(int, int) {
$if linux {
$if linux || macos {
pipefd := [2]int{}
if C.pipe(&pipefd[0]) != 0 {
return error('error ${C.errno}: ' + os.posix_get_error_msg(C.errno))
@ -16,8 +16,8 @@ fn make_pipe() !(int, int) {
}
fn test_level_trigger() {
// currently only linux is supported
$if linux {
// currently only linux and macos are supported
$if linux || macos {
mut notifier := notify.new()!
reader, writer := make_pipe()!
defer {
@ -37,8 +37,8 @@ fn test_level_trigger() {
}
fn test_edge_trigger() {
// currently only linux is supported
$if linux {
// currently only linux and macos are supported
$if linux || macos {
mut notifier := notify.new()!
reader, writer := make_pipe()!
defer {
@ -53,7 +53,27 @@ fn test_edge_trigger() {
os.fd_write(writer, 'foobar')
check_read_event(mut n, reader, 'foo')
assert notifier.wait(0).len == 0
$if linux {
assert notifier.wait(0).len == 0
}
$if macos {
/*
In the kqueue of macos, EV_CLEAR flag represents a clear event,
which is mainly used for pipeline and socket class events. When this flag is set,
kqueue will trigger the corresponding event when the data is readable or writable,
but it is not guaranteed that the event will only be triggered once.
Compared to EPOLLET, EV_CLEAR's behavior varies. In epoll, the edge triggered mode only triggers
an event once when the state changes from unreadable/non writable to readable/writable,
that is, when the data changes from unreadable to readable,
or when the data changes from unreadable to writable. In the kqueue of macos,
EV_CLEAR does not possess this precise edge triggering behavior.
Therefore, in the kqueue of macos, even if the data is not completely read,
it is possible to continue triggering read events. This means that if you don't process all the data,
the next kqueue event notification may still be triggered
*/
// notifier.wait(0).len == 1 or 0
}
os.fd_write(writer, 'baz')
// we do not get an event because there is still data
@ -65,7 +85,7 @@ fn test_edge_trigger() {
}
fn test_one_shot() {
$if linux {
$if linux || macos {
mut notifier := notify.new()!
reader, writer := make_pipe()!
defer {
@ -89,6 +109,7 @@ fn test_one_shot() {
}
}
// Kqueue does not support 'hangup' event type.
fn test_hangup() {
$if linux {
mut notifier := notify.new()!
@ -112,7 +133,7 @@ fn test_hangup() {
}
fn test_write() {
$if linux {
$if linux || macos {
mut notifier := notify.new()!
reader, writer := make_pipe()!
defer {
@ -133,7 +154,7 @@ fn test_write() {
}
fn test_remove() {
$if linux {
$if linux || macos {
mut notifier := notify.new()!
reader, writer := make_pipe()!
defer {

View File

@ -262,8 +262,7 @@ fn ptr_win_get_error_msg(code u32) voidptr {
return buf
}
C.FormatMessage(os.format_message_allocate_buffer | os.format_message_from_system | os.format_message_ignore_inserts,
0, code, C.MAKELANGID(os.lang_neutral, os.sublang_default), voidptr(&buf), 0,
0)
0, code, 0, voidptr(&buf), 0, 0)
return buf
}

View File

@ -23,7 +23,7 @@ pub mut:
fd int
loop_id int = -1
events u32
cb fn (int, int, voidptr)
cb fn (int, int, voidptr) = unsafe { nil }
// used internally by the kqueue implementation
backend int
}
@ -31,7 +31,7 @@ pub mut:
pub struct Config {
pub:
port int = 8080
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil }
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
user_data voidptr = unsafe { nil }
timeout_secs int = 8
@ -42,7 +42,7 @@ pub:
[heap]
pub struct Picoev {
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response)
cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response) = unsafe { nil }
err_cb fn (voidptr, picohttpparser.Request, mut picohttpparser.Response, IError) = default_err_cb
user_data voidptr = unsafe { nil }

View File

@ -28,7 +28,7 @@ const time_to_test = time.Time{
hour: 21
minute: 23
second: 42
microsecond: 123456
nanosecond: 123456789
unix: 332198622
}
@ -38,6 +38,7 @@ assert '1980-07-11 21:23' == time_to_test.format()
assert '1980-07-11 21:23:42' == time_to_test.format_ss()
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli()
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro()
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano()
```
You can also parse strings to produce time.Time values,

View File

@ -5,7 +5,34 @@ fn test_custom_format() {
assert date.custom_format('YYYY-MM-DD HH:mm') == date.format()
assert date.custom_format('MMM') == date.smonth()
test_str := 'M MM Mo MMM MMMM\nD DD DDD DDDD\nd dd ddd dddd\nYY YYYY a A\nH HH h hh k kk e\nm mm s ss Z ZZ ZZZ\nDo DDDo Q Qo QQ\nN NN w wo ww\nM/D/YYYY N-HH:mm:ss Qo?a'
test_str := 'M MM Mo MMM MMMM\nD DD DDD DDDD\nd dd ddd dddd\nYY YYYY a A\nH HH h hh k kk i ii e\nm mm s ss Z ZZ ZZZ\nDo DDDo Q Qo QQ\nN NN w wo ww\nM/D/YYYY N-HH:mm:ss Qo?a'
println(date.custom_format(test_str))
}
fn test_hours() {
assert time.parse('2023-08-04 00:00:45')!.custom_format('ii A i a hh A h a') == '00 AM 0 am 12 AM 12 am'
assert time.parse('2023-08-04 01:00:45')!.custom_format('ii A i a hh A h a') == '01 AM 1 am 01 AM 1 am'
assert time.parse('2023-08-04 02:00:45')!.custom_format('ii A i a hh A h a') == '02 AM 2 am 02 AM 2 am'
assert time.parse('2023-08-04 03:00:45')!.custom_format('ii A i a hh A h a') == '03 AM 3 am 03 AM 3 am'
assert time.parse('2023-08-04 04:00:45')!.custom_format('ii A i a hh A h a') == '04 AM 4 am 04 AM 4 am'
assert time.parse('2023-08-04 05:00:45')!.custom_format('ii A i a hh A h a') == '05 AM 5 am 05 AM 5 am'
assert time.parse('2023-08-04 06:00:45')!.custom_format('ii A i a hh A h a') == '06 AM 6 am 06 AM 6 am'
assert time.parse('2023-08-04 07:00:45')!.custom_format('ii A i a hh A h a') == '07 AM 7 am 07 AM 7 am'
assert time.parse('2023-08-04 08:00:45')!.custom_format('ii A i a hh A h a') == '08 AM 8 am 08 AM 8 am'
assert time.parse('2023-08-04 09:00:45')!.custom_format('ii A i a hh A h a') == '09 AM 9 am 09 AM 9 am'
assert time.parse('2023-08-04 10:00:45')!.custom_format('ii A i a hh A h a') == '10 AM 10 am 10 AM 10 am'
assert time.parse('2023-08-04 11:00:45')!.custom_format('ii A i a hh A h a') == '11 AM 11 am 11 AM 11 am'
assert time.parse('2023-08-04 12:00:45')!.custom_format('ii A i a hh A h a') == '12 PM 12 pm 12 PM 12 pm'
assert time.parse('2023-08-04 13:00:45')!.custom_format('ii A i a hh A h a') == '01 PM 1 pm 01 PM 1 pm'
assert time.parse('2023-08-04 14:00:45')!.custom_format('ii A i a hh A h a') == '02 PM 2 pm 02 PM 2 pm'
assert time.parse('2023-08-04 15:00:45')!.custom_format('ii A i a hh A h a') == '03 PM 3 pm 03 PM 3 pm'
assert time.parse('2023-08-04 16:00:45')!.custom_format('ii A i a hh A h a') == '04 PM 4 pm 04 PM 4 pm'
assert time.parse('2023-08-04 17:00:45')!.custom_format('ii A i a hh A h a') == '05 PM 5 pm 05 PM 5 pm'
assert time.parse('2023-08-04 18:00:45')!.custom_format('ii A i a hh A h a') == '06 PM 6 pm 06 PM 6 pm'
assert time.parse('2023-08-04 19:00:45')!.custom_format('ii A i a hh A h a') == '07 PM 7 pm 07 PM 7 pm'
assert time.parse('2023-08-04 20:00:45')!.custom_format('ii A i a hh A h a') == '08 PM 8 pm 08 PM 8 pm'
assert time.parse('2023-08-04 21:00:45')!.custom_format('ii A i a hh A h a') == '09 PM 9 pm 09 PM 9 pm'
assert time.parse('2023-08-04 22:00:45')!.custom_format('ii A i a hh A h a') == '10 PM 10 pm 10 PM 10 pm'
assert time.parse('2023-08-04 23:00:45')!.custom_format('ii A i a hh A h a') == '11 PM 11 pm 11 PM 11 pm'
}

View File

@ -31,3 +31,9 @@ fn test_duration_str() {
assert time.Duration(1 * time.hour + 5 * time.second).str() == '1:00:05'
assert time.Duration(168 * time.hour + 5 * time.minute + 7 * time.second).str() == '168:05:07'
}
fn test_duration_debug() {
assert time.Duration(1 * time.nanosecond).debug() == 'Duration: 1ns'
assert time.Duration(169 * time.hour + 5 * time.minute + 7 * time.second).debug() == 'Duration: 7days, 1h, 5m, 7s'
assert (-time.Duration(169 * time.hour + 5 * time.minute + 7 * time.second)).debug() == 'Duration: - 7days, 1h, 5m, 7s'
}

View File

@ -17,7 +17,17 @@ pub fn (t Time) format_ss() string {
// format_ss_milli returns a date string in "YYYY-MM-DD HH:mm:ss.123" format (24h).
pub fn (t Time) format_ss_milli() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}'
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000_000):03d}'
}
// format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h).
pub fn (t Time) format_ss_micro() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}'
}
// format_ss_nano returns a date string in "YYYY-MM-DD HH:mm:ss.123456789" format (24h).
pub fn (t Time) format_ss_nano() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond:09d}'
}
// format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
@ -25,12 +35,13 @@ pub fn (t Time) format_ss_milli() string {
// It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols.
pub fn (t Time) format_rfc3339() string {
u := t.local_to_utc()
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.microsecond / 1000):03d}Z'
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond / 1_000_000):03d}Z'
}
// format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h).
pub fn (t Time) format_ss_micro() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}'
// format_rfc3339_nano returns a date string in "YYYY-MM-DDTHH:mm:ss.123456789Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
pub fn (t Time) format_rfc3339_nano() string {
u := t.local_to_utc()
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond):09d}Z'
}
// hhmm returns a date string in "HH:mm" format (24h).
@ -85,8 +96,8 @@ fn ordinal_suffix(n int) string {
}
const (
tokens_2 = ['MM', 'Mo', 'DD', 'Do', 'YY', 'ss', 'kk', 'NN', 'mm', 'hh', 'HH', 'ZZ', 'dd', 'Qo',
'QQ', 'wo', 'ww']
tokens_2 = ['MM', 'Mo', 'DD', 'Do', 'YY', 'ss', 'kk', 'NN', 'mm', 'hh', 'HH', 'ii', 'ZZ', 'dd',
'Qo', 'QQ', 'wo', 'ww']
tokens_3 = ['MMM', 'DDD', 'ZZZ', 'ddd']
tokens_4 = ['MMMM', 'DDDD', 'DDDo', 'dddd', 'YYYY']
)
@ -127,6 +138,8 @@ const (
// | | HH | 00 01 ... 22 23 |
// | | h | 1 2 ... 11 12 |
// | | hh | 01 02 ... 11 12 |
// | | i | 0 1 ... 11 12 1 ... 11 |
// | | ii | 00 01 ... 11 12 01 ... 11 |
// | | k | 1 2 ... 23 24 |
// | | kk | 01 02 ... 23 24 |
// | **Minute** | m | 0 1 ... 58 59 |
@ -221,10 +234,20 @@ pub fn (t Time) custom_format(s string) string {
sb.write_string('${t.hour:02}')
}
'h' {
sb.write_string((t.hour % 12).str())
h := (t.hour + 11) % 12 + 1
sb.write_string(h.str())
}
'hh' {
sb.write_string('${(t.hour % 12):02}')
h := (t.hour + 11) % 12 + 1
sb.write_string('${h:02}')
}
'i' {
h := if t.hour > 12 { t.hour - 12 } else { t.hour }
sb.write_string(h.str())
}
'ii' {
h := if t.hour > 12 { t.hour - 12 } else { t.hour }
sb.write_string('${h:02}')
}
'm' {
sb.write_string(t.minute.str())
@ -306,17 +329,17 @@ pub fn (t Time) custom_format(s string) string {
}
}
'a' {
if t.hour > 12 {
sb.write_string('pm')
} else {
if t.hour < 12 {
sb.write_string('am')
} else {
sb.write_string('pm')
}
}
'A' {
if t.hour > 12 {
sb.write_string('PM')
} else {
if t.hour < 12 {
sb.write_string('AM')
} else {
sb.write_string('PM')
}
}
else {
@ -379,8 +402,9 @@ pub fn (t Time) get_fmt_time_str(fmt_time FormatTime) string {
.hhmm24 { '${t.hour:02d}:${t.minute:02d}' }
.hhmmss12 { '${hour_}:${t.minute:02d}:${t.second:02d} ${tp}' }
.hhmmss24 { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' }
.hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.microsecond / 1000):03d}' }
.hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.microsecond:06d}' }
.hhmmss24_milli { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000_000):03d}' }
.hhmmss24_micro { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}' }
.hhmmss24_nano { '${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond:06d}' }
else { 'unknown enumeration ${fmt_time}' }
}
}

View File

@ -3,19 +3,22 @@ module time
// operator `==` returns true if provided time is equal to time
[inline]
pub fn (t1 Time) == (t2 Time) bool {
return t1.unix == t2.unix && t1.microsecond == t2.microsecond
return t1.unix == t2.unix && t1.nanosecond == t2.nanosecond
}
// operator `<` returns true if provided time is less than time
[inline]
pub fn (t1 Time) < (t2 Time) bool {
return t1.unix < t2.unix || (t1.unix == t2.unix && t1.microsecond < t2.microsecond)
return t1.unix < t2.unix || (t1.unix == t2.unix && t1.nanosecond < t2.nanosecond)
}
// Time subtract using operator overloading.
[inline]
pub fn (lhs Time) - (rhs Time) Duration {
lhs_micro := lhs.unix * 1_000_000 + lhs.microsecond
rhs_micro := rhs.unix * 1_000_000 + rhs.microsecond
return (lhs_micro - rhs_micro) * microsecond
// lhs.unix * 1_000_000_000 + i64(lhs.nanosecond) will overflow i64, for years > 3000 .
// Doing the diff first, and *then* multiplying by `second`, is less likely to overflow,
// since lhs and rhs will be likely close to each other.
unixs := i64(lhs.unix - rhs.unix) * second
nanos := lhs.nanosecond - rhs.nanosecond
return unixs + nanos
}

View File

@ -39,7 +39,7 @@ fn test_time1_should_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t2 := new_time(Time{
year: 2000
@ -48,7 +48,7 @@ fn test_time1_should_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
assert t1 == t2
}
@ -61,9 +61,9 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -71,7 +71,7 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -80,7 +80,7 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -90,7 +90,7 @@ fn test_time1_should_not_be_same_as_time2() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 != t2
assert t3 != t4
@ -104,9 +104,9 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 3
microsecond: 102
nanosecond: 102
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -114,7 +114,7 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -123,7 +123,7 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 5
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -133,7 +133,7 @@ fn test_time1_should_be_greater_than_time2() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 > t2
assert t3 > t4
@ -147,9 +147,9 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 3
microsecond: 102
nanosecond: 102
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -157,7 +157,7 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -166,7 +166,7 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -176,7 +176,7 @@ fn test_time2_should_be_less_than_time1() {
hour: 22
minute: 11
second: 2
microsecond: 0
nanosecond: 0
})
assert t2 < t1
assert t4 < t3
@ -190,9 +190,9 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 3
microsecond: 102
nanosecond: 102
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -200,7 +200,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -209,7 +209,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 5
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -219,7 +219,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_gt() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 >= t2
assert t3 >= t4
@ -233,9 +233,9 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -243,7 +243,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t3 := new_time(Time{
year: 2000
@ -252,7 +252,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -262,7 +262,7 @@ fn test_time1_should_be_greater_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
assert t1 >= t2
assert t3 >= t4
@ -276,9 +276,9 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -286,7 +286,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 3
microsecond: 101
nanosecond: 101
})
t3 := new_time(Time{
year: 2000
@ -295,7 +295,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -305,7 +305,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_lt() {
hour: 22
minute: 11
second: 4
microsecond: 0
nanosecond: 0
})
assert t1 <= t2
assert t3 <= t4
@ -319,9 +319,9 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
// Difference is one microsecond
// Difference is one nanosecond
t2 := new_time(Time{
year: 2000
month: 5
@ -329,7 +329,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t3 := new_time(Time{
year: 2000
@ -338,7 +338,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
// Difference is one second
t4 := new_time(Time{
@ -348,7 +348,7 @@ fn test_time1_should_be_less_or_equal_to_time2_when_eq() {
hour: 22
minute: 11
second: 3
microsecond: 0
nanosecond: 0
})
assert t1 <= t2
assert t3 <= t4
@ -362,7 +362,7 @@ fn test_time2_copied_from_time1_should_be_equal() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t2 := new_time(t1)
assert t2 == t1
@ -370,8 +370,8 @@ fn test_time2_copied_from_time1_should_be_equal() {
fn test_subtract() {
d_seconds := 3
d_microseconds := 13
duration := d_seconds * second + d_microseconds * microsecond
d_nanoseconds := 13
duration := d_seconds * second + d_nanoseconds * nanosecond
t1 := new_time(Time{
year: 2000
month: 5
@ -379,9 +379,9 @@ fn test_subtract() {
hour: 22
minute: 11
second: 3
microsecond: 100
nanosecond: 100
})
t2 := unix2(i64(t1.unix) + d_seconds, t1.microsecond + d_microseconds)
t2 := unix_nanosecond(i64(t1.unix) + d_seconds, t1.nanosecond + d_nanoseconds)
d1 := t2 - t1
d2 := t1 - t2
assert d1 > 0

View File

@ -35,13 +35,13 @@ pub fn parse_rfc3339(s string) !Time {
}
// Check if sn is time only
if !parts[0].contains('-') && parts[0].contains(':') {
mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true
hour_, minute_, second_, microsecond_, unix_offset, is_local_time = parse_iso8601_time(parts[0])!
mut hour_, mut minute_, mut second_, mut microsecond_, mut nanosecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, 0, i64(0), true
hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time = parse_iso8601_time(parts[0])!
t = new_time(Time{
hour: hour_
minute: minute_
second: second_
microsecond: microsecond_
nanosecond: nanosecond_
})
if is_local_time {
return t // Time is already local time
@ -52,7 +52,7 @@ pub fn parse_rfc3339(s string) !Time {
} else if unix_offset > 0 {
unix_time += unix_offset
}
t = unix2(i64(unix_time), t.microsecond)
t = unix_nanosecond(i64(unix_time), t.nanosecond)
return t
}
@ -171,9 +171,9 @@ pub fn parse_iso8601(s string) !Time {
return error_invalid_time(12, 'malformed date')
}
year, month, day := parse_iso8601_date(parts[0])!
mut hour_, mut minute_, mut second_, mut microsecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, i64(0), true
mut hour_, mut minute_, mut second_, mut microsecond_, mut nanosecond_, mut unix_offset, mut is_local_time := 0, 0, 0, 0, 0, i64(0), true
if parts.len == 2 {
hour_, minute_, second_, microsecond_, unix_offset, is_local_time = parse_iso8601_time(parts[1])!
hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time = parse_iso8601_time(parts[1])!
}
mut t := new_time(
year: year
@ -182,7 +182,7 @@ pub fn parse_iso8601(s string) !Time {
hour: hour_
minute: minute_
second: second_
microsecond: microsecond_
nanosecond: nanosecond_
)
if is_local_time {
return t // Time already local time
@ -193,7 +193,7 @@ pub fn parse_iso8601(s string) !Time {
} else if unix_offset > 0 {
unix_time += unix_offset
}
t = unix2(i64(unix_time), t.microsecond)
t = unix_nanosecond(i64(unix_time), t.nanosecond)
return t
}
@ -237,7 +237,7 @@ fn parse_iso8601_date(s string) !(int, int, int) {
return year, month, day
}
fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) {
fn parse_iso8601_time(s string) !(int, int, int, int, int, i64, bool) {
hour_ := 0
minute_ := 0
second_ := 0
@ -281,6 +281,7 @@ fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) {
if count < 4 {
return error_invalid_time(10, 'malformed date')
}
nanosecond_ = microsecond_ * 1000
}
is_local_time := plus_min_z == `a` && count == 4
is_utc := plus_min_z == `Z` && count == 5
@ -300,5 +301,6 @@ fn parse_iso8601_time(s string) !(int, int, int, int, i64, bool) {
if plus_min_z == `+` {
unix_offset *= -1
}
return hour_, minute_, second_, microsecond_, unix_offset, is_local_time
// eprintln('parse_iso8601_time s: $s | hour_: $hour_ | minute_: $minute_ | second_: $second_ | microsecond_: $microsecond_ | nanosecond_: $nanosecond_ | unix_offset: $unix_offset | is_local_time: $is_local_time')
return hour_, minute_, second_, microsecond_, nanosecond_, unix_offset, is_local_time
}

View File

@ -65,11 +65,11 @@ fn test_parse_iso8601() {
]
times := [
[2020, 6, 5, 15, 38, 6, 0],
[2020, 6, 5, 15, 38, 6, 15959],
[2020, 6, 5, 15, 38, 6, 15959],
[2020, 6, 5, 13, 38, 6, 15959],
[2020, 6, 5, 17, 38, 6, 15959],
[2020, 11, 5, 15, 38, 6, 15959],
[2020, 6, 5, 15, 38, 6, 15959000],
[2020, 6, 5, 15, 38, 6, 15959000],
[2020, 6, 5, 13, 38, 6, 15959000],
[2020, 6, 5, 17, 38, 6, 15959000],
[2020, 11, 5, 15, 38, 6, 15959000],
]
for i, format in formats {
t := time.parse_iso8601(format) or {
@ -89,8 +89,8 @@ fn test_parse_iso8601() {
assert t.minute == minute
second := times[i][5]
assert t.second == second
microsecond := times[i][6]
assert t.microsecond == microsecond
nanosecond := times[i][6]
assert t.nanosecond == nanosecond
}
}
@ -107,7 +107,7 @@ fn test_parse_iso8601_local() {
assert t.hour == 15
assert t.minute == 38
assert t.second == 6
assert t.microsecond == 15959
assert t.nanosecond == 15959_000
}
fn test_parse_iso8601_invalid() {
@ -145,7 +145,7 @@ fn test_parse_iso8601_date_only() {
assert t.hour == 0
assert t.minute == 0
assert t.second == 0
assert t.microsecond == 0
assert t.nanosecond == 0
}
fn check_invalid_date(s string) {

View File

@ -53,13 +53,6 @@ pub fn utc() Time {
return solaris_utc()
}
return linux_utc()
/*
// defaults to most common feature, the microsecond precision is not available
// in this API call
t := C.time(0)
_ = C.time(&t)
return unix2(i64(t), 0)
*/
}
// new_time returns a time struct with the calculated Unix time.
@ -90,7 +83,7 @@ pub fn ticks() i64 {
} $else {
ts := C.timeval{}
C.gettimeofday(&ts, 0)
return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1000)))
return i64(ts.tv_sec * u64(1000) + (ts.tv_usec / u64(1_000)))
}
// t := i64(C.mach_absolute_time())
// # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t );
@ -105,7 +98,7 @@ pub fn (t Time) str() string {
}
// convert_ctime converts a C time to V time.
fn convert_ctime(t C.tm, microsecond int) Time {
fn convert_ctime(t C.tm, nanosecond int) Time {
return Time{
year: t.tm_year + 1900
month: t.tm_mon + 1
@ -113,7 +106,7 @@ fn convert_ctime(t C.tm, microsecond int) Time {
hour: t.tm_hour
minute: t.tm_min
second: t.tm_sec
microsecond: microsecond
nanosecond: nanosecond
unix: make_unix_time(t)
// for the actual code base when we
// call convert_ctime, it is always

View File

@ -40,15 +40,17 @@ pub const (
// Time contains various time units for a point in time.
pub struct Time {
pub:
year int
month int
day int
hour int
minute int
second int
microsecond int
unix i64
is_local bool // used to make time.now().local().local() == time.now().local()
year int
month int
day int
hour int
minute int
second int
nanosecond int
unix i64
is_local bool // used to make time.now().local().local() == time.now().local()
//
microsecond int [deprecated: 'use t.nanosecond / 1000 instead'; deprecated_after: '2023-08-05']
}
// FormatDelimiter contains different time formats.
@ -59,6 +61,7 @@ pub enum FormatTime {
hhmmss24
hhmmss24_milli
hhmmss24_micro
hhmmss24_nano
no_time
}
@ -99,7 +102,7 @@ pub fn (t Time) smonth() string {
return time.months_string[i * 3..(i + 1) * 3]
}
// unix_time returns the UNIX time.
// unix_time returns the UNIX time with second resolution.
[inline]
pub fn (t Time) unix_time() i64 {
return t.unix
@ -108,18 +111,39 @@ pub fn (t Time) unix_time() i64 {
// unix_time_milli returns the UNIX time with millisecond resolution.
[inline]
pub fn (t Time) unix_time_milli() i64 {
return t.unix * 1000 + (t.microsecond / 1000)
return t.unix * 1_000 + (i64(t.nanosecond) / 1_000_000)
}
// unix_time_micro returns the UNIX time with microsecond resolution.
[inline]
pub fn (t Time) unix_time_micro() i64 {
return t.unix * 1_000_000 + (i64(t.nanosecond) / 1_000)
}
// unix_time_nano returns the UNIX time with nanosecond resolution.
[inline]
pub fn (t Time) unix_time_nano() i64 {
// TODO: use i128 here, when V supports it, since the following expression overflows for years like 3001:
return t.unix * 1_000_000_000 + i64(t.nanosecond)
}
// add returns a new time with the given duration added.
pub fn (t Time) add(d Duration) Time {
microseconds := i64(t.unix) * 1_000_000 + t.microsecond + d.microseconds()
unix := microseconds / 1_000_000
micro := microseconds % 1_000_000
if t.is_local {
return unix2(unix, int(micro)).as_local()
// This expression overflows i64 for big years (and we do not have i128 yet):
// nanos := t.unix * 1_000_000_000 + i64(t.nanosecond) <-
// ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯
mut unixs := t.unix
mut nanos := i64(t.nanosecond) + d.nanoseconds()
unixs += nanos / time.second
nanos = nanos % time.second
if nanos < 0 {
unixs--
nanos += time.second
}
return unix2(unix, int(micro))
if t.is_local {
return unix_nanosecond(unixs, int(nanos)).as_local()
}
return unix_nanosecond(unixs, int(nanos))
}
// add_seconds returns a new time struct with an added number of seconds.
@ -311,9 +335,9 @@ pub fn days_in_month(month int, year int) !int {
return res
}
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss microsecond: micros unix: unix }`)
// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix }`)
pub fn (t Time) debug() string {
return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} microsecond: ${t.microsecond:06} unix: ${t.unix:07} }'
return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} }'
}
// A lot of these are taken from the Go library.
@ -326,6 +350,7 @@ pub const (
second = Duration(1000 * millisecond)
minute = Duration(60 * second)
hour = Duration(60 * minute)
// day = Duration(24 * hour)
infinite = Duration(i64(9223372036854775807))
)
@ -348,23 +373,22 @@ pub fn (d Duration) milliseconds() i64 {
// consider all of them in sub-one intervals
// seconds returns the duration as a floating point number of seconds.
pub fn (d Duration) seconds() f64 {
sec := d / time.second
nsec := d % time.second
return f64(sec) + f64(nsec) / time.second
return f64(d) / f64(time.second)
}
// minutes returns the duration as a floating point number of minutes.
pub fn (d Duration) minutes() f64 {
min := d / time.minute
nsec := d % time.minute
return f64(min) + f64(nsec) / time.minute
return f64(d) / f64(time.minute)
}
// hours returns the duration as a floating point number of hours.
pub fn (d Duration) hours() f64 {
hr := d / time.hour
nsec := d % time.hour
return f64(hr) + f64(nsec) / time.hour
return f64(d) / f64(time.hour)
}
// days returns the duration as a floating point number of days.
pub fn (d Duration) days() f64 {
return f64(d) / f64(time.hour * 24)
}
// str pretty prints the duration
@ -412,6 +436,35 @@ pub fn (d Duration) str() string {
return '${ns}ns'
}
// debug returns a detailed breakdown of the Duration, as: 'Duration: - 50days, 4h, 3m, 7s, 541ms, 78us, 9ns'
pub fn (d Duration) debug() string {
mut res := []string{}
mut x := i64(d)
mut sign := ''
if x < 0 {
sign = '- '
x = -x
}
for label, v in {
'days': 24 * time.hour
'h': time.hour
'm': time.minute
's': time.second
'ms': time.millisecond
'us': time.microsecond
} {
if x > v {
xx := x / v
x = x % v
res << xx.str() + label
}
}
if x > 0 {
res << '${x}ns'
}
return 'Duration: ${sign}${res.join(', ')}'
}
// offset returns time zone UTC offset in seconds.
pub fn offset() int {
t := utc()

View File

@ -3,8 +3,6 @@ import time
fn test_add_to_day_in_the_previous_century() {
a := time.parse_iso8601('1900-01-01')!
aa := a.add_days(180)
dump(a.debug())
dump(aa.debug())
assert aa.ymmdd() == '1900-06-30'
}
@ -23,6 +21,8 @@ fn test_add_to_day_in_the_recent_past() {
fn test_add_to_day_in_the_future_1() {
a := time.parse_iso8601('3000-11-01')!
aa := a.add_days(180)
dump(a.debug())
dump(aa.debug())
assert aa.ymmdd() == '3001-04-30'
}

View File

@ -2,11 +2,10 @@ module time
#include <mach/mach_time.h>
const (
// start_time is needed on Darwin and Windows because of potential overflows
start_time = C.mach_absolute_time()
time_base = init_time_base()
)
// start_time is needed on Darwin and Windows because of potential overflows
const start_time = C.mach_absolute_time()
const time_base = init_time_base()
[typedef]
struct C.mach_timebase_info_data_t {
@ -25,11 +24,6 @@ struct InternalTimeBase {
denom u32 = 1
}
pub struct C.timeval {
tv_sec u64
tv_usec u64
}
fn init_time_base() C.mach_timebase_info_data_t {
tb := C.mach_timebase_info_data_t{}
C.mach_timebase_info(&tb)
@ -62,29 +56,22 @@ fn vpc_now_darwin() u64 {
return (tm - time.start_time) * time.time_base.numer / time.time_base.denom
}
// darwin_now returns a better precision current time for Darwin based operating system
// this should be implemented with native system calls eventually
// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get
// the microseconds seconds part and converts to local time
// darwin_now returns a better precision current time for macos
fn darwin_now() Time {
// get the high precision time as UTC clock
tv := C.timeval{}
C.gettimeofday(&tv, 0)
// get the high precision time as UTC realtime clock, and use the nanoseconds part
mut ts := C.timespec{}
C.clock_gettime(C.CLOCK_REALTIME, &ts)
loc_tm := C.tm{}
asec := voidptr(&tv.tv_sec)
C.localtime_r(asec, &loc_tm)
return convert_ctime(loc_tm, int(tv.tv_usec))
C.localtime_r(voidptr(&ts.tv_sec), &loc_tm)
return convert_ctime(loc_tm, int(ts.tv_nsec))
}
// darwin_utc returns a better precision current time for Darwin based operating system
// this should be implemented with native system calls eventually
// but for now a bit tweaky. It uses the deprecated gettimeofday clock to get
// the microseconds seconds part and normal local time to get correct local time
// darwin_utc returns a better precision current time for macos
fn darwin_utc() Time {
// get the high precision time as UTC clock
tv := C.timeval{}
C.gettimeofday(&tv, 0)
return unix2(i64(tv.tv_sec), int(tv.tv_usec))
mut ts := C.timespec{}
C.clock_gettime(C.CLOCK_REALTIME, &ts)
return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
}
// dummy to compile with all compilers

View File

@ -36,7 +36,7 @@ pub fn (t Time) local() Time {
}
loc_tm := C.tm{}
C.localtime_r(voidptr(&t.unix), &loc_tm)
return convert_ctime(loc_tm, t.microsecond)
return convert_ctime(loc_tm, t.nanosecond)
}
// in most systems, these are __quad_t, which is an i64
@ -58,7 +58,7 @@ pub fn sys_mono_now() u64 {
} $else {
ts := C.timespec{}
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec)
return u64(ts.tv_sec) * 1_000_000_000 + u64(ts.tv_nsec)
}
}
@ -68,7 +68,7 @@ pub fn sys_mono_now() u64 {
fn vpc_now() u64 {
ts := C.timespec{}
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec)
return u64(ts.tv_sec) * 1_000_000_000 + u64(ts.tv_nsec)
}
// The linux_* functions are placed here, since they're used on Android as well
@ -83,7 +83,7 @@ fn linux_now() Time {
C.clock_gettime(C.CLOCK_REALTIME, &ts)
loc_tm := C.tm{}
C.localtime_r(voidptr(&ts.tv_sec), &loc_tm)
return convert_ctime(loc_tm, int(ts.tv_nsec / 1000))
return convert_ctime(loc_tm, int(ts.tv_nsec))
}
fn linux_utc() Time {
@ -91,7 +91,7 @@ fn linux_utc() Time {
// and use the nanoseconds part
mut ts := C.timespec{}
C.clock_gettime(C.CLOCK_REALTIME, &ts)
return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000))
return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
}
// dummy to compile with all compilers
@ -104,12 +104,6 @@ fn win_utc() Time {
return Time{}
}
// dummy to compile with all compilers
pub struct C.timeval {
tv_sec u64
tv_usec u64
}
// return absolute timespec for now()+d
pub fn (d Duration) timespec() C.timespec {
mut ts := C.timespec{}

View File

@ -10,7 +10,7 @@ fn solaris_now() Time {
C.clock_gettime(C.CLOCK_REALTIME, &ts)
loc_tm := C.tm{}
C.localtime_r(voidptr(&ts.tv_sec), &loc_tm)
return convert_ctime(loc_tm, int(ts.tv_nsec / 1000))
return convert_ctime(loc_tm, int(ts.tv_nsec))
}
fn solaris_utc() Time {
@ -18,7 +18,7 @@ fn solaris_utc() Time {
// and use the nanoseconds part
mut ts := C.timespec{}
C.clock_gettime(C.CLOCK_REALTIME, &ts)
return unix2(i64(ts.tv_sec), int(ts.tv_nsec / 1000))
return unix_nanosecond(i64(ts.tv_sec), int(ts.tv_nsec))
}
// dummy to compile with all compilers

View File

@ -1,18 +1,16 @@
import time
import math
const (
time_to_test = time.Time{
year: 1980
month: 7
day: 11
hour: 21
minute: 23
second: 42
microsecond: 123456
unix: 332198622
}
)
const time_to_test = time.Time{
year: 1980
month: 7
day: 11
hour: 21
minute: 23
second: 42
nanosecond: 123456789
unix: 332198622
}
fn test_is_leap_year() {
// 1996 % 4 = 0 and 1996 % 100 > 0
@ -83,6 +81,21 @@ fn test_unix() {
assert t6.second == 29
}
fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z'
res := time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
}
fn test_format_rfc3339_nano() {
res := time_to_test.format_rfc3339_nano()
assert res.ends_with('23:42.123456789Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
}
fn test_format_ss() {
assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy)
}
@ -93,20 +106,18 @@ fn test_format_ss_milli() {
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli()
}
fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z'
res := time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1')
assert res.contains('T')
}
fn test_format_ss_micro() {
assert '11.07.1980 21:23:42.123456' == time_to_test.get_fmt_str(.dot, .hhmmss24_micro,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro()
}
fn test_format_ss_nano() {
assert '11.07.1980 21:23:42.123456789' == time_to_test.get_fmt_str(.dot, .hhmmss24_nano,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano()
}
fn test_smonth() {
month_names := ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov',
'Dec']
@ -180,21 +191,29 @@ fn test_weekday_str() {
fn test_add() {
d_seconds := 3
d_microseconds := 13
duration := time.Duration(d_seconds * time.second + d_microseconds * time.microsecond)
d_nanoseconds := 13
duration := time.Duration(d_seconds * time.second + d_nanoseconds * time.nanosecond)
// dump(duration.debug())
t1 := time_to_test
// dump(t1.debug())
t2 := time_to_test.add(duration)
// dump(t2.debug())
assert t2.second == t1.second + d_seconds
assert t2.microsecond == t1.microsecond + d_microseconds
assert t2.nanosecond == t1.nanosecond + d_nanoseconds
assert t2.unix == t1.unix + d_seconds
assert t2.is_local == t1.is_local
//
t3 := time_to_test.add(-duration)
// dump(t3.debug())
assert t3.second == t1.second - d_seconds
assert t3.microsecond == t1.microsecond - d_microseconds
assert t3.nanosecond == t1.nanosecond - d_nanoseconds
assert t3.unix == t1.unix - d_seconds
assert t3.is_local == t1.is_local
//
t4 := time_to_test.as_local()
// dump(t4.debug())
t5 := t4.add(duration)
// dump(t5.debug())
assert t5.is_local == t4.is_local
}
@ -220,13 +239,14 @@ fn test_now() {
assert now.minute < 60
assert now.second >= 0
assert now.second <= 60 // <= 60 cause of leap seconds
assert now.microsecond >= 0
assert now.microsecond < 1000000
assert now.nanosecond >= 0
assert now.nanosecond < time.second
}
fn test_utc() {
now := time.utc()
// The year the test was built
// dump(now.debug())
assert now.year >= 2020
assert now.month > 0
assert now.month <= 12
@ -234,20 +254,20 @@ fn test_utc() {
assert now.minute < 60
assert now.second >= 0
assert now.second <= 60 // <= 60 cause of leap seconds
assert now.microsecond >= 0
assert now.microsecond < 1000000
assert now.nanosecond >= 0
assert now.nanosecond < time.second
}
fn test_unix_time() {
t1 := time.utc()
time.sleep(50 * time.millisecond)
t2 := time.utc()
eprintln('t1: ${t1}')
eprintln('t2: ${t2}')
eprintln(' t1: ${t1}')
eprintln(' t2: ${t2}')
ut1 := t1.unix_time()
ut2 := t2.unix_time()
eprintln('ut1: ${ut1}')
eprintln('ut2: ${ut2}')
eprintln(' ut1: ${ut1}')
eprintln(' ut2: ${ut2}')
assert ut2 - ut1 < 2
//
utm1 := t1.unix_time_milli()

View File

@ -39,6 +39,8 @@ fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation &C.TIME_ZONE_INFORMAT
fn C.localtime_s(t &C.time_t, tm &C.tm)
fn C.timespec_get(t &C.timespec, base int) int
const (
// start_time is needed on Darwin and Windows because of potential overflows
start_time = init_win_time_start()
@ -107,7 +109,7 @@ pub fn (t Time) local() Time {
hour: u16(t.hour)
minute: u16(t.minute)
second: u16(t.second)
millisecond: u16(t.microsecond / 1000)
millisecond: u16(t.nanosecond / 1_000_000)
}
st_local := SystemTime{}
C.SystemTimeToTzSpecificLocalTime(unsafe { nil }, &st_utc, &st_local)
@ -118,7 +120,7 @@ pub fn (t Time) local() Time {
hour: st_local.hour
minute: st_local.minute
second: st_local.second // These are the same
microsecond: int(st_local.millisecond) * 1000
nanosecond: int(st_local.millisecond) * 1_000_000
unix: st_local.unix_time()
}
return t_local
@ -141,7 +143,7 @@ fn win_now() Time {
hour: st_local.hour
minute: st_local.minute
second: st_local.second
microsecond: int(st_local.millisecond) * 1000
nanosecond: int(st_local.millisecond) * 1_000_000
unix: st_local.unix_time()
is_local: true
}
@ -163,7 +165,7 @@ fn win_utc() Time {
hour: st_utc.hour
minute: st_utc.minute
second: st_utc.second
microsecond: int(st_utc.millisecond) * 1000
nanosecond: int(st_utc.millisecond) * 1_000_000
unix: st_utc.unix_time()
is_local: false
}
@ -213,12 +215,6 @@ fn solaris_utc() Time {
return Time{}
}
// dummy to compile with all compilers
pub struct C.timeval {
tv_sec u64
tv_usec u64
}
// sleep makes the calling thread sleep for a given duration (in nanoseconds).
pub fn sleep(duration Duration) {
C.Sleep(int(duration / millisecond))

View File

@ -3,7 +3,7 @@
// that can be found in the LICENSE file.
module time
// unix returns a time struct from Unix time.
// unix returns a time struct from an Unix timestamp (number of seconds since 1970-01-01)
pub fn unix(abs i64) Time {
// Split into day and time
mut day_offset := abs / seconds_per_day
@ -24,8 +24,20 @@ pub fn unix(abs i64) Time {
}
}
// unix2 returns a time struct from Unix time and microsecond value
// unix2 returns a Time struct, given an Unix timestamp in seconds, and a microsecond value
[deprecated: 'use unix_microsecond(unix_ts, us) instead']
[deprecated_after: '2023-09-05']
pub fn unix2(abs i64, microsecond int) Time {
return unix_nanosecond(abs, microsecond * 1000)
}
// unix_microsecond returns a Time struct, given an Unix timestamp in seconds, and a microsecond value
pub fn unix_microsecond(abs i64, microsecond int) Time {
return unix_nanosecond(abs, microsecond * 1000)
}
// unix_nanosecond returns a Time struct, given an Unix timestamp in seconds, and a nanosecond value
pub fn unix_nanosecond(abs i64, nanosecond int) Time {
// Split into day and time
mut day_offset := abs / seconds_per_day
if abs % seconds_per_day < 0 {
@ -41,7 +53,7 @@ pub fn unix2(abs i64, microsecond int) Time {
hour: hr
minute: min
second: sec
microsecond: microsecond
nanosecond: nanosecond
unix: abs
}
}

View File

@ -89,6 +89,13 @@ pub fn (t &Table) stringify_fn_decl(node &FnDecl, cur_mod string, m2a map[string
f.write_string('pub ')
}
f.write_string('fn ')
pre_comments := node.comments.filter(it.pos.pos < node.name_pos.pos)
if pre_comments.len > 0 {
write_comments(pre_comments, mut f)
if !f.last_n(1)[0].is_space() {
f.write_string(' ')
}
}
if node.is_method {
f.write_string('(')
mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
@ -126,6 +133,7 @@ pub fn (t &Table) stringify_fn_decl(node &FnDecl, cur_mod string, m2a map[string
fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_mod string, m2a map[string]string) {
mut add_para_types := true
mut is_wrap_needed := false
if node.generic_names.len > 0 {
if node.is_method {
sym := t.sym(node.params[0].typ)
@ -161,6 +169,20 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m
is_type_only := param.name == ''
should_add_type := true // is_last_param || is_type_only || node.params[i + 1].typ != param.typ ||
// (node.is_variadic && i == node.params.len - 2)
pre_comments := param.comments.filter(it.pos.pos < param.pos.pos)
if pre_comments.len > 0 {
if i == 0 && !pre_comments.last().is_inline {
is_wrap_needed = true
f.write_string('\n\t')
}
write_comments(pre_comments, mut f)
if !f.last_n(1)[0].is_space() {
f.write_string(' ')
}
}
if is_wrap_needed {
f.write_string('\t')
}
if param.is_mut {
f.write_string(param.typ.share().str() + ' ')
}
@ -211,13 +233,50 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m
}
}
fn write_comments(comments []Comment, mut f strings.Builder) {
for i, c in comments {
if !f.last_n(1)[0].is_space() {
f.write_string(' ')
}
write_comment(c, mut f)
if c.is_inline && i < comments.len - 1 && !c.is_multi {
f.write_string(' ')
} else if (!c.is_inline || c.is_multi) && i < comments.len - 1 {
f.writeln('')
}
}
}
fn write_comment(node Comment, mut f strings.Builder) {
if node.is_inline {
x := node.text.trim_left('\x01').trim_space()
if x.contains('\n') {
f.writeln('/*')
f.writeln(x)
f.write_string('*/')
} else {
f.write_string('/* ${x} */')
}
} else {
mut s := node.text.trim_left('\x01').trim_right(' ')
mut out_s := '//'
if s != '' {
if s[0].is_letter() || s[0].is_digit() {
out_s += ' '
}
out_s += s
}
f.writeln(out_s)
}
}
struct StringifyModReplacement {
mod string
alias string
weight int
}
pub fn shorten_full_name_based_on_aliases(input string, m2a map[string]string) string {
fn shorten_full_name_based_on_aliases(input string, m2a map[string]string) string {
if m2a.len == 0 || -1 == input.index_u8(`.`) {
// a simple typename, like `string` or `[]bool`; no module aliasings apply,
// (or there just are not any mappings)

View File

@ -526,6 +526,9 @@ fn (mut c Checker) alias_type_decl(node ast.AliasTypeDecl) {
// type Sum = int | Alias
// type Alias = Sum
}
.none_ {
c.error('cannot create a type alias of `none` as it is a value', node.type_pos)
}
// The rest of the parent symbol kinds are also allowed, since they are either primitive types,
// that in turn do not allow recursion, or are abstract enough so that they can not be checked at comptime:
else {}
@ -1143,6 +1146,8 @@ fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Typ
c.check_expr_opt_call(expr.expr, ret_type)
} else if expr is ast.AsCast {
c.check_expr_opt_call(expr.expr, ret_type)
} else if expr is ast.ParExpr {
c.check_expr_opt_call(expr.expr, ret_type)
}
return ret_type
}
@ -1774,9 +1779,6 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) {
useen << uval
}
}
ast.PrefixExpr {
dump(field.expr)
}
ast.InfixExpr {
// Handle `enum Foo { x = 1 + 2 }`
c.infix_expr(mut field.expr)

View File

@ -490,6 +490,9 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type {
c.error('unknown function: ${node.name}', node.pos)
}
}
// If the left expr has an or_block, it needs to be checked for legal or_block statement.
return_type := c.expr(mut node.left)
c.check_expr_opt_call(node.left, return_type)
// TODO merge logic from method_call and fn_call
// First check everything that applies to both fns and methods
old_inside_fn_arg := c.inside_fn_arg
@ -1779,7 +1782,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
// cannot hide interface expected type to make possible to pass its interface type automatically
earg_types << if targ.idx() != param.typ.idx() { param.typ } else { targ }
} else {
earg_types << targ
earg_types << param.typ
}
param_share := param.typ.share()
if param_share == .shared_t

View File

@ -77,7 +77,7 @@ fn (mut c Checker) struct_decl(mut node ast.StructDecl) {
if sym.kind == .function {
if !field.typ.has_flag(.option) && !field.has_default_expr
&& field.attrs.filter(it.name == 'required').len == 0 {
error_msg := 'uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)'
error_msg := 'uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or `[required]` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)'
c.note(error_msg, field.pos)
}
}

View File

@ -1,4 +1,4 @@
vlib/v/checker/tests/fn_check_for_matching_option_result_in_fields.vv:2:2: notice: uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)
vlib/v/checker/tests/fn_check_for_matching_option_result_in_fields.vv:2:2: notice: uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or `[required]` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)
1 | struct Abc {
2 | f fn (voidptr)
| ~~~~~~~~~~~~~~

View File

@ -1,10 +1,17 @@
vlib/v/checker/tests/generics_struct_init_err.vv:14:2: notice: uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)
vlib/v/checker/tests/generics_struct_init_err.vv:14:2: notice: uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or `[required]` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)
12 |
13 | struct FnHolder2[T] {
14 | func fn (int) int
| ~~~~~~~~~~~~~~~~~
15 | }
16 |
vlib/v/checker/tests/generics_struct_init_err.vv:58:8: error: cannot initialize builtin type `FnHolder1[neg]`
56 | ret = holder_call_12(neg, 3)
57 | assert ret == -3
58 | ret = FnHolder1{neg}.call(4)
| ~~~~~~~~~~~~~~
59 | assert ret == -4
60 |
vlib/v/checker/tests/generics_struct_init_err.vv:67:8: error: could not infer generic type `T` in generic struct `FnHolder2[T]`
65 | ret = holder_call_22(neg, 5)
66 | assert ret == -5

View File

@ -1,6 +1,6 @@
vlib/v/checker/tests/option_type_call_err.vv:4:5: error: Result type cannot be called directly
2 |
vlib/v/checker/tests/option_type_call_err.vv:4:5: error: os.ls() returns a Result, so it should have either an `or {}` block, or `!` at the end
2 |
3 | fn main() {
4 | os.ls('.').filter(it.ends_with('.v')) or { return }
| ~~~~~~~
5 | }
5 | }

View File

@ -0,0 +1,27 @@
vlib/v/checker/tests/or_block_check_err.vv:6:36: error: assignment requires a non empty `or {}` block
4 |
5 | fn main() {
6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b')
| ~~~~~
7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b')
8 |
vlib/v/checker/tests/or_block_check_err.vv:7:37: error: assignment requires a non empty `or {}` block
5 | fn main() {
6 | _ = callexpr_with_or_block_call() or {}.replace('a', 'b')
7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b')
| ~~~~~
8 |
9 | _ = callexpr_with_or_block_call() or { eprintln('error') }.replace('a', 'b')
vlib/v/checker/tests/or_block_check_err.vv:9:41: error: `or` block must provide a default value of type `string`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)
7 | _ = (callexpr_with_or_block_call() or {}).replace('a', 'b')
8 |
9 | _ = callexpr_with_or_block_call() or { eprintln('error') }.replace('a', 'b')
| ~~~~~~~~~~~~~~~~~
10 | _ = (callexpr_with_or_block_call() or { eprintln('error') }).replace('a', 'b')
11 | }
vlib/v/checker/tests/or_block_check_err.vv:10:42: error: `or` block must provide a default value of type `string`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)
8 |
9 | _ = callexpr_with_or_block_call() or { eprintln('error') }.replace('a', 'b')
10 | _ = (callexpr_with_or_block_call() or { eprintln('error') }).replace('a', 'b')
| ~~~~~~~~~~~~~~~~~
11 | }

View File

@ -0,0 +1,11 @@
fn callexpr_with_or_block_call() !string {
return error('')
}
fn main() {
_ = callexpr_with_or_block_call() or {}.replace('a', 'b')
_ = (callexpr_with_or_block_call() or {}).replace('a', 'b')
_ = callexpr_with_or_block_call() or { eprintln('error') }.replace('a', 'b')
_ = (callexpr_with_or_block_call() or { eprintln('error') }).replace('a', 'b')
}

View File

@ -1,5 +1,5 @@
vlib/v/checker/tests/result_type_call_err.vv:12:2: error: Result type cannot be called directly
10 |
vlib/v/checker/tests/result_type_call_err.vv:12:2: error: new_foo() returns a Result, so it should have either an `or {}` block, or `!` at the end
10 |
11 | fn main() {
12 | new_foo().foo()
| ~~~~~~~~~

View File

@ -0,0 +1,3 @@
vlib/v/checker/tests/type_alias_none_parent_type_err.vv:1:13: error: cannot create a type alias of `none` as it is a value
1 | type None = none
| ~~~~

View File

@ -0,0 +1 @@
type None = none

View File

@ -1019,7 +1019,7 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) {
pub fn (mut f Fmt) fn_decl(node ast.FnDecl) {
f.attrs(node.attrs)
f.fn_header(node)
f.write(f.table.stringify_fn_decl(&node, f.cur_mod, f.mod2alias))
// Handle trailing comments after fn header declarations
if node.no_body && node.end_comments.len > 0 {
first_comment := node.end_comments[0]
@ -1043,136 +1043,6 @@ pub fn (mut f Fmt) fn_decl(node ast.FnDecl) {
f.fn_body(node)
}
pub fn (mut f Fmt) fn_header(node ast.FnDecl) {
if node.is_pub {
f.write('pub ')
}
f.write('fn ')
pre_comments := node.comments.filter(it.pos.pos < node.name_pos.pos)
if pre_comments.len > 0 {
f.comments(pre_comments)
f.write(' ')
}
if node.is_method {
f.write('(')
mut styp := util.no_cur_mod(f.table.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
f.cur_mod)
if node.rec_mut {
f.write(node.receiver.typ.share().str() + ' ')
styp = styp[1..] // remove &
}
f.write(node.receiver.name + ' ')
styp = util.no_cur_mod(styp, f.cur_mod)
if node.params[0].is_auto_rec {
styp = styp.trim('&')
}
f.write(styp + ') ')
} else if node.is_static_type_method {
mut styp := util.no_cur_mod(f.table.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
f.cur_mod)
f.write(styp + '.')
}
mut name := if !node.is_method && node.language == .v {
node.name.all_after_last('.')
} else {
node.name
}
if node.is_static_type_method {
name = name.after('__static__')
}
f.write(name)
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] {
f.write(' ')
}
mut add_para_types := true
if node.generic_names.len > 0 {
if node.is_method {
sym := f.table.sym(node.params[0].typ)
if sym.info is ast.Struct {
generic_names := sym.info.generic_types.map(f.table.sym(it).name)
if generic_names == node.generic_names {
add_para_types = false
}
}
}
if add_para_types {
f.write('[')
for i, gname in node.generic_names {
is_last := i == node.generic_names.len - 1
f.write(gname)
if !is_last {
f.write(', ')
}
}
f.write(']')
}
}
f.write('(')
for i, arg in node.params {
before_comments := arg.comments.filter(it.pos.pos < arg.pos.pos)
if before_comments.len > 0 {
f.comments(before_comments, level: .indent)
}
// skip receiver
if node.is_method && i == 0 {
continue
}
if arg.is_hidden {
continue
}
is_last_arg := i == node.params.len - 1
is_type_only := arg.name == ''
should_add_type := true
if arg.is_mut {
f.write(arg.typ.share().str() + ' ')
}
f.write(arg.name)
arg_sym := f.table.sym(arg.typ)
if arg_sym.kind == .struct_ && (arg_sym.info as ast.Struct).is_anon {
f.write(' struct {')
struct_ := arg_sym.info as ast.Struct
for field in struct_.fields {
f.write(' ${field.name} ${f.table.type_to_str(field.typ)}')
if field.has_default_expr {
f.write(' = ${field.default_expr}')
}
}
if struct_.fields.len > 0 {
f.write(' ')
}
f.write('}')
} else {
mut s := f.table.type_to_str(arg.typ.clear_flag(.shared_f))
if arg.is_mut {
if s.starts_with('&') && ((!arg_sym.is_number() && arg_sym.kind != .bool)
|| node.language != .v) {
s = s[1..]
}
}
s = util.no_cur_mod(s, f.cur_mod)
s = ast.shorten_full_name_based_on_aliases(s, f.mod2alias)
if should_add_type {
if !is_type_only {
f.write(' ')
}
if node.is_variadic && is_last_arg {
f.write('...')
}
f.write(s)
}
}
if !is_last_arg {
f.write(', ')
}
}
f.write(')')
if node.return_type != ast.void_type {
sreturn_type := util.no_cur_mod(f.table.type_to_str(node.return_type), f.cur_mod)
short_sreturn_type := ast.shorten_full_name_based_on_aliases(sreturn_type, f.mod2alias)
f.write(' ${short_sreturn_type}')
}
}
pub fn (mut f Fmt) anon_fn(node ast.AnonFn) {
f.write(f.table.stringify_anon_decl(&node, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast
f.fn_body(node.decl)

View File

@ -0,0 +1,19 @@
const a = 123
const b = 'abc'
const c = rune(123)
const d = u64(2 * c)
const f = func()
fn func() int {
return 42
}
dump(a)
dump(b)
dump(c)
dump(d)
dump(f)

View File

@ -0,0 +1,19 @@
const a := 123
const b := 'abc'
const c := rune(123)
const d := u64(2 * c)
const f := func()
fn func() int {
return 42
}
dump(a)
dump(b)
dump(c)
dump(d)
dump(f)

View File

@ -0,0 +1,7 @@
module main
fn main() {
println('Hello World!')
}
fn C.f( /* mut */ buff &char) i64

View File

@ -0,0 +1,7 @@
module main
fn main() {
println('Hello World!')
}
fn C.f(/*mut*/buff &char) i64

View File

@ -3,6 +3,6 @@ fn /* main */ main() {
}
fn // hi
print_hi() {
print_hi() {
println('hi')
}

View File

@ -0,0 +1,8 @@
fn foo(
// Foo
s string) {
}
fn bar( /* p1 */ a string, /* p2 */ b int) {
println('hello')
}

View File

@ -0,0 +1,8 @@
fn foo(
// Foo
s string) {
}
fn bar(/*p1*/a string, /*p2*/b int) {
println('hello')
}

View File

@ -1188,12 +1188,12 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
}
mut typ_sym := g.table.sym(unwrapped_rec_type)
// non-option alias type that undefined this method (not include `str`) need to use parent type
if !left_type.has_flag(.option) && typ_sym.kind == .alias && node.name != 'str'
if !left_type.has_flag(.option) && mut typ_sym.info is ast.Alias && node.name != 'str'
&& !typ_sym.has_method(node.name) {
unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type
unwrapped_rec_type = typ_sym.info.parent_type
typ_sym = g.table.sym(unwrapped_rec_type)
} else if typ_sym.kind == .array && !typ_sym.has_method(node.name) && node.name != 'str' {
typ := g.table.unaliased_type((typ_sym.info as ast.Array).elem_type)
} else if mut typ_sym.info is ast.Array && !typ_sym.has_method(node.name) && node.name != 'str' {
typ := g.table.unaliased_type(typ_sym.info.elem_type)
typ_idx := g.table.find_type_idx(g.table.array_name(typ))
if typ_idx > 0 {
unwrapped_rec_type = ast.Type(typ_idx)
@ -1206,7 +1206,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
}
rec_cc_type := g.cc_type(unwrapped_rec_type, false)
mut receiver_type_name := util.no_dots(rec_cc_type)
if typ_sym.kind == .interface_ && (typ_sym.info as ast.Interface).defines_method(node.name) {
if mut typ_sym.info is ast.Interface && typ_sym.info.defines_method(node.name) {
// Speaker_name_table[s._interface_idx].speak(s._object)
$if debug_interface_method_call ? {
eprintln('>>> interface typ_sym.name: ${typ_sym.name} | receiver_type_name: ${receiver_type_name} | pos: ${node.pos}')
@ -1312,11 +1312,10 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
mut name := util.no_dots('${receiver_type_name}_${node.name}')
mut array_depth := -1
mut noscan := ''
if left_sym.kind == .array {
if left_sym.info is ast.Array {
needs_depth := node.name in ['clone', 'repeat']
if needs_depth {
elem_type := (left_sym.info as ast.Array).elem_type
array_depth = g.get_array_depth(elem_type)
array_depth = g.get_array_depth(left_sym.info.elem_type)
}
maybe_noscan := needs_depth
|| node.name in ['pop', 'push', 'push_many', 'reverse', 'grow_cap', 'grow_len']

View File

@ -13,7 +13,7 @@ enum Builtin {
}
struct BuiltinFn {
body fn (builtin BuiltinFn, mut g Gen)
body fn (builtin BuiltinFn, mut g Gen) = unsafe { nil }
arg_regs []Register
mut:
calls []i64 // call addresses

View File

@ -83,8 +83,10 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
if unsupported {
g.warning('opcodes format: xx xx xx xx\nhash statements are not allowed with the native backend, use the C backend for extended C interoperability.',
node.pos)
if !g.pref.experimental {
g.warning('opcodes format: xx xx xx xx\nhash statements are not allowed with the native backend, use the C backend for extended C interoperability.',
node.pos)
}
}
}
ast.Module {}
@ -98,7 +100,9 @@ fn (mut g Gen) stmt(node ast.Stmt) {
g.gen_assert(node)
}
ast.GlobalDecl {
g.warning('globals are not supported yet', node.pos)
if !g.pref.experimental {
g.warning('globals are not supported yet', node.pos)
}
}
ast.Import {} // do nothing here
ast.StructDecl {}

View File

@ -11,7 +11,12 @@ recognized by the heuristic:
tool_version = '1.2.1'
version: '0.2.42'
VERSION = "1.23.8"
If certain lines need to be skipped, use the --skip option. For instance,
the following command will skip lines containing "tool-version":
v bump --patch --skip "tool-version" [files...]
Examples:
Bump the patch version in v.mod if it exists
v bump --patch
@ -22,7 +27,8 @@ Examples:
Options:
-h, --help Show this help text.
-m, --major Bump the major version.
-n, --minor Bump the minor version.
-p, --patch Bump the patch version.
-h, --help Show this help text.
-m, --major Bump the major version.
-n, --minor Bump the minor version.
-p, --patch Bump the patch version.
-s, --skip <string> Skip lines matching this substring.

View File

@ -12,8 +12,8 @@ pub:
vopts string // v compiler options for a live shared library
original string // full path to the original source file, compiled with -live
live_fn_mutex voidptr // the address of the C mutex, that locks the [live] fns during reloads.
live_linkfn FNLinkLiveSymbols // generated C callback; receives a dlopen handle
so_extension string // .so or .dll
live_linkfn FNLinkLiveSymbols = unsafe { nil } // generated C callback; receives a dlopen handle
so_extension string // .so or .dll
so_name_template string // a template for the shared libraries location
pub mut:
monitored_files []string // an array, containing all paths that should be monitored for changes

View File

@ -948,6 +948,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) {
is_mut: is_mut
typ: param_type
type_pos: type_pos
comments: comments
}
param_no++
if param_no > 1024 {

View File

@ -3713,10 +3713,13 @@ fn (mut p Parser) const_decl() ast.ConstDecl {
if p.tok.kind == .comma {
p.error_with_pos('const declaration do not support multiple assign yet', p.tok.pos())
}
// Allow for `const x := 123`, and for `const x = 123` too.
// Supporting `const x := 123` in addition to `const x = 123`, makes extracting local variables to constants much less annoying, while prototyping:
if p.tok.kind == .decl_assign {
p.error_with_pos('cannot use `:=` to declare a const, use `=` instead', p.tok.pos())
p.check(.decl_assign)
} else {
p.check(.assign)
}
p.check(.assign)
end_comments << p.eat_comments()
if p.tok.kind == .key_fn {
p.error('const initializer fn literal is not a constant')

View File

@ -1,5 +0,0 @@
vlib/v/parser/tests/const_init_decl_assign_err.vv:2:4: error: cannot use `:=` to declare a const, use `=` instead
1 | const (
2 | a := 43
| ~~
3 | )

View File

@ -1,3 +0,0 @@
const (
a := 43
)

View File

@ -38,6 +38,7 @@ pub mut:
is_inter_start bool // for hacky string interpolation TODO simplify
is_inter_end bool
is_enclosed_inter bool
is_nested_enclosed_inter bool
line_comment string
last_lt int = -1 // position of latest <
is_started bool
@ -648,7 +649,7 @@ fn (mut s Scanner) text_scan() token.Token {
}
// End of $var, start next string
if s.is_inter_end {
if s.text[s.pos] == s.quote {
if s.text[s.pos] == s.quote || (s.text[s.pos] == s.inter_quote && s.is_enclosed_inter) {
s.is_inter_end = false
return s.new_token(.string, '', 1)
}
@ -818,7 +819,7 @@ fn (mut s Scanner) text_scan() token.Token {
return s.new_token(.lcbr, '', 1)
}
`$` {
if s.is_inside_string {
if s.is_inside_string || s.is_enclosed_inter {
return s.new_token(.str_dollar, '', 1)
} else {
return s.new_token(.dollar, '', 1)
@ -827,7 +828,7 @@ fn (mut s Scanner) text_scan() token.Token {
`}` {
// s = `hello $name !`
// s = `hello ${name} !`
if s.is_enclosed_inter && s.inter_cbr_count == 0 {
if (s.is_enclosed_inter || s.is_nested_enclosed_inter) && s.inter_cbr_count == 0 {
if s.pos < s.text.len - 1 {
s.pos++
} else {
@ -835,10 +836,18 @@ fn (mut s Scanner) text_scan() token.Token {
}
if s.text[s.pos] == s.quote {
s.is_inside_string = false
s.is_enclosed_inter = false
if s.is_nested_enclosed_inter {
s.is_nested_enclosed_inter = false
} else {
s.is_enclosed_inter = false
}
return s.new_token(.string, '', 1)
}
s.is_enclosed_inter = false
if s.is_nested_enclosed_inter {
s.is_nested_enclosed_inter = false
} else {
s.is_enclosed_inter = false
}
s.just_closed_inter = true
ident_string := s.ident_string()
return s.new_token(.string, ident_string, ident_string.len + 2) // + two quotes
@ -1229,7 +1238,11 @@ fn (mut s Scanner) ident_string() string {
if prevc == `$` && c == `{` && !is_raw
&& s.count_symbol_before(s.pos - 2, scanner.backslash) % 2 == 0 {
s.is_inside_string = true
s.is_enclosed_inter = true
if s.is_enclosed_inter {
s.is_nested_enclosed_inter = true
} else {
s.is_enclosed_inter = true
}
// so that s.pos points to $ at the next step
s.pos -= 2
break
@ -1472,17 +1485,23 @@ fn (mut s Scanner) ident_char() string {
u := c.runes()
if u.len != 1 {
if escaped_hex || escaped_unicode {
s.error('invalid character literal `${orig}` => `${c}` (${u}) (escape sequence did not refer to a singular rune)')
s.error_with_pos('invalid character literal `${orig}` => `${c}` (${u}) (escape sequence did not refer to a singular rune)',
lspos)
} else if u.len == 0 {
s.add_error_detail_with_pos('use quotes for strings, backticks for characters',
lspos)
s.error('invalid empty character literal `${orig}`')
s.error_with_pos('invalid empty character literal `${orig}`', lspos)
} else {
s.add_error_detail_with_pos('use quotes for strings, backticks for characters',
lspos)
s.error('invalid character literal `${orig}` => `${c}` (${u}) (more than one character)')
s.error_with_pos('invalid character literal `${orig}` => `${c}` (${u}) (more than one character)',
lspos)
}
}
} else if c == '\n' {
s.add_error_detail_with_pos('use quotes for strings, backticks for characters',
lspos)
s.error_with_pos('invalid character literal, use \`\\n\` instead', lspos)
}
// Escapes a `'` character
if c == "'" {
@ -1566,15 +1585,19 @@ fn (mut s Scanner) eat_details() string {
}
pub fn (mut s Scanner) warn(msg string) {
if s.pref.warns_are_errors {
s.error(msg)
return
}
pos := token.Pos{
line_nr: s.line_nr
pos: s.pos
col: s.current_column() - 1
}
s.warn_with_pos(msg, pos)
}
pub fn (mut s Scanner) warn_with_pos(msg string, pos token.Pos) {
if s.pref.warns_are_errors {
s.error_with_pos(msg, pos)
return
}
details := s.eat_details()
if s.pref.output_mode == .stdout && !s.pref.check_only {
util.show_compiler_message('warning:',
@ -1604,6 +1627,10 @@ pub fn (mut s Scanner) error(msg string) {
pos: s.pos
col: s.current_column() - 1
}
s.error_with_pos(msg, pos)
}
pub fn (mut s Scanner) error_with_pos(msg string, pos token.Pos) {
details := s.eat_details()
if s.pref.output_mode == .stdout && !s.pref.check_only {
util.show_compiler_message('error:',

View File

@ -1,10 +1,10 @@
vlib/v/scanner/tests/empty_character_literal_err.vv:2:8: error: invalid empty character literal ``
vlib/v/scanner/tests/empty_character_literal_err.vv:2:7: error: invalid empty character literal ``
1 | fn main() {
2 | a := ``
| ^
| ^
3 | println(a)
4 | }
Details:
Details:
vlib/v/scanner/tests/empty_character_literal_err.vv:2:7: details: use quotes for strings, backticks for characters
1 | fn main() {
2 | a := ``

View File

@ -0,0 +1,13 @@
vlib/v/scanner/tests/newline_character_literal_err.vv:2:7: error: invalid character literal, use `\n` instead
1 | fn main() {
2 | a := `
| ^
3 | `
4 | println(a)
Details:
vlib/v/scanner/tests/newline_character_literal_err.vv:2:7: details: use quotes for strings, backticks for characters
1 | fn main() {
2 | a := `
| ^
3 | `
4 | println(a)

View File

@ -0,0 +1,5 @@
fn main() {
a := `
`
println(a)
}

View File

@ -0,0 +1,7 @@
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:5] 2 + 2: 4
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:6] 2 * 3 + 50 / 5: 16
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:7] 3.14 + 0.1: 3.24
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:8] 'abc' + 'a': abca
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:9] 'a' + 'b' + 'c': abc
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:10] true || (false && true): true
[vlib/v/slow_tests/inout/dump_expressions_with_literals.vv:11] 2 == 4: false

View File

@ -0,0 +1,12 @@
// Note: dump expressions should not get optimised out by the transformer stage,
// even though they could normally, when they are composed of literals, i.e.
// the value of the expression is known at compile time.
fn main() {
dump(2 + 2)
dump(2 * 3 + 50 / 5)
dump(3.14 + 0.1)
dump('abc' + 'a')
dump('a' + 'b' + 'c')
dump(true || (false && true))
dump(2 == 4)
}

View File

@ -0,0 +1,38 @@
fn f(x int, s string) string {
return 'label ${s}: ${x}'
}
// vfmt off
fn test_string_interp_with_inner_quotes() {
x := 'hi'
println('abc ${f(123, 'def')} xyz')
assert 'abc ${f(123, 'def')} xyz' == 'abc label def: 123 xyz'
println('abc ${f(123, "def")} xyz')
assert 'abc ${f(123, "def")} xyz' == 'abc label def: 123 xyz'
println("abc ${f(123, 'def')} xyz")
assert "abc ${f(123, 'def')} xyz" == 'abc label def: 123 xyz'
println("abc ${f(123, "def")} xyz")
assert "abc ${f(123, "def")} xyz" == 'abc label def: 123 xyz'
println("abc ${f(123, "$x $x")} xyz")
assert "abc ${f(123, "$x $x")} xyz" == 'abc label hi hi: 123 xyz'
println('abc ${f(123, '$x $x')} xyz')
assert 'abc ${f(123, '$x $x')} xyz' == 'abc label hi hi: 123 xyz'
println('abc ${f(123, "$x $x")} xyz')
assert 'abc ${f(123, "$x $x")} xyz' == 'abc label hi hi: 123 xyz'
println("abc ${f(123, '$x $x')} xyz")
assert "abc ${f(123, '$x $x')} xyz" == 'abc label hi hi: 123 xyz'
println("abc ${f(123, "${x} ${x}")} xyz")
assert "abc ${f(123, "${x} ${x}")} xyz" == 'abc label hi hi: 123 xyz'
println('abc ${f(123, '${x} ${x}')} xyz')
assert 'abc ${f(123, '${x} ${x}')} xyz' == 'abc label hi hi: 123 xyz'
}
// vfmt on

View File

@ -0,0 +1,18 @@
struct Foo {
f fn (Foo) int = dummy
}
fn dummy(s Foo) int {
return 22
}
fn (mut s Foo) fun() int {
return s.f(s)
}
fn test_struct_field_fn_call() {
mut s := Foo{}
ret := s.fun()
println(ret)
assert ret == 22
}

View File

@ -10,7 +10,8 @@ pub mut:
index &IndexState
table &ast.Table = unsafe { nil }
mut:
is_assert bool
is_assert bool
inside_dump bool
}
pub fn new_transformer(pref_ &pref.Preferences) &Transformer {
@ -513,6 +514,9 @@ pub fn (mut t Transformer) interface_decl(mut node ast.InterfaceDecl) ast.Stmt {
}
pub fn (mut t Transformer) expr(mut node ast.Expr) ast.Expr {
if t.inside_dump {
return node
}
match mut node {
ast.AnonFn {
node.decl = t.stmt(mut node.decl) as ast.FnDecl
@ -563,7 +567,10 @@ pub fn (mut t Transformer) expr(mut node ast.Expr) ast.Expr {
}
}
ast.DumpExpr {
old_inside_dump := t.inside_dump
t.inside_dump = true
node.expr = t.expr(mut node.expr)
t.inside_dump = old_inside_dump
}
ast.GoExpr {
node.call_expr = t.expr(mut node.call_expr) as ast.CallExpr

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