mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
Compare commits
36 Commits
428fd7f57f
...
65a493d023
Author | SHA1 | Date | |
---|---|---|---|
|
65a493d023 | ||
|
76b4c92848 | ||
|
d0cc564089 | ||
|
f915366ac4 | ||
|
3211a653c3 | ||
|
eef9b5f168 | ||
|
64029a2980 | ||
|
b7afe6b236 | ||
|
6813a12339 | ||
|
10df697d32 | ||
|
68f18fcb8e | ||
|
f4859ffb11 | ||
|
3b3395d93b | ||
|
8db1aaafd5 | ||
|
286d39706b | ||
|
320057df1c | ||
|
07b36d69f3 | ||
|
6045a1db02 | ||
|
357ac0bb5a | ||
|
7c2f3e4530 | ||
|
23a7c40c44 | ||
|
c3f7fe39ec | ||
|
7ca23f6316 | ||
|
b9a523cefd | ||
|
cc97b8df1e | ||
|
8e26ca3f5a | ||
|
da7a9bc8ae | ||
|
f72cb00b74 | ||
|
e5cd1724f9 | ||
|
301320f4b0 | ||
|
4cf8328f71 | ||
|
d91c7f1b3b | ||
|
ffdd5bb955 | ||
|
5bb02b3dd7 | ||
|
598992b208 | ||
|
e3ade704cb |
1
.github/workflows/macos_ci.yml
vendored
1
.github/workflows/macos_ci.yml
vendored
@ -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: |
|
||||
|
@ -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 ... -------------------')
|
||||
|
@ -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"')
|
||||
|
@ -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
|
||||
|
@ -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}" ')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
7
examples/call_v_from_ruby/README.md
Normal file
7
examples/call_v_from_ruby/README.md
Normal 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 .
|
23
examples/call_v_from_ruby/test.rb
Normal file
23
examples/call_v_from_ruby/test.rb
Normal 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)
|
13
examples/call_v_from_ruby/test.v
Normal file
13
examples/call_v_from_ruby/test.v
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
module mysql
|
||||
|
||||
pub struct Result {
|
||||
pub:
|
||||
result &C.MYSQL_RES = unsafe { nil }
|
||||
}
|
||||
|
||||
|
@ -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)!
|
||||
```
|
||||
|
@ -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)
|
||||
|
@ -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;')!
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
228
vlib/os/notify/backend_darwin.c.v
Normal file
228
vlib/os/notify/backend_darwin.c.v
Normal 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
|
||||
}
|
@ -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
12
vlib/os/notify/kqueue.h
Normal 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
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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}' }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
109
vlib/time/time.v
109
vlib/time/time.v
@ -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()
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
| ~~~~~~~~~~~~~~
|
||||
|
@ -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
|
||||
|
@ -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 | }
|
||||
|
27
vlib/v/checker/tests/or_block_check_err.out
Normal file
27
vlib/v/checker/tests/or_block_check_err.out
Normal 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 | }
|
11
vlib/v/checker/tests/or_block_check_err.vv
Normal file
11
vlib/v/checker/tests/or_block_check_err.vv
Normal 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')
|
||||
}
|
@ -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()
|
||||
| ~~~~~~~~~
|
||||
|
3
vlib/v/checker/tests/type_alias_none_parent_type_err.out
Normal file
3
vlib/v/checker/tests/type_alias_none_parent_type_err.out
Normal 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
|
||||
| ~~~~
|
1
vlib/v/checker/tests/type_alias_none_parent_type_err.vv
Normal file
1
vlib/v/checker/tests/type_alias_none_parent_type_err.vv
Normal file
@ -0,0 +1 @@
|
||||
type None = none
|
132
vlib/v/fmt/fmt.v
132
vlib/v/fmt/fmt.v
@ -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)
|
||||
|
19
vlib/v/fmt/tests/allow_const_with_decl_assign_expected.vv
Normal file
19
vlib/v/fmt/tests/allow_const_with_decl_assign_expected.vv
Normal 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)
|
19
vlib/v/fmt/tests/allow_const_with_decl_assign_input.vv
Normal file
19
vlib/v/fmt/tests/allow_const_with_decl_assign_input.vv
Normal 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)
|
7
vlib/v/fmt/tests/c_fn_headers_with_comments_expected.vv
Normal file
7
vlib/v/fmt/tests/c_fn_headers_with_comments_expected.vv
Normal file
@ -0,0 +1,7 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
println('Hello World!')
|
||||
}
|
||||
|
||||
fn C.f( /* mut */ buff &char) i64
|
7
vlib/v/fmt/tests/c_fn_headers_with_comments_input.vv
Normal file
7
vlib/v/fmt/tests/c_fn_headers_with_comments_input.vv
Normal file
@ -0,0 +1,7 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
println('Hello World!')
|
||||
}
|
||||
|
||||
fn C.f(/*mut*/buff &char) i64
|
@ -3,6 +3,6 @@ fn /* main */ main() {
|
||||
}
|
||||
|
||||
fn // hi
|
||||
print_hi() {
|
||||
print_hi() {
|
||||
println('hi')
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
fn foo(
|
||||
// Foo
|
||||
s string) {
|
||||
}
|
||||
|
||||
fn bar( /* p1 */ a string, /* p2 */ b int) {
|
||||
println('hello')
|
||||
}
|
8
vlib/v/fmt/tests/fn_headers_with_param_comments_input.vv
Normal file
8
vlib/v/fmt/tests/fn_headers_with_param_comments_input.vv
Normal file
@ -0,0 +1,8 @@
|
||||
fn foo(
|
||||
// Foo
|
||||
s string) {
|
||||
}
|
||||
|
||||
fn bar(/*p1*/a string, /*p2*/b int) {
|
||||
println('hello')
|
||||
}
|
@ -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']
|
||||
|
@ -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
|
||||
|
@ -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 {}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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')
|
||||
|
@ -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 | )
|
@ -1,3 +0,0 @@
|
||||
const (
|
||||
a := 43
|
||||
)
|
@ -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:',
|
||||
|
@ -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 := ``
|
||||
|
13
vlib/v/scanner/tests/newline_character_literal_err.out
Normal file
13
vlib/v/scanner/tests/newline_character_literal_err.out
Normal 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)
|
5
vlib/v/scanner/tests/newline_character_literal_err.vv
Normal file
5
vlib/v/scanner/tests/newline_character_literal_err.vv
Normal file
@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
a := `
|
||||
`
|
||||
println(a)
|
||||
}
|
@ -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
|
12
vlib/v/slow_tests/inout/dump_expressions_with_literals.vv
Normal file
12
vlib/v/slow_tests/inout/dump_expressions_with_literals.vv
Normal 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)
|
||||
}
|
38
vlib/v/tests/string_interpolation_with_inner_quotes_test.v
Normal file
38
vlib/v/tests/string_interpolation_with_inner_quotes_test.v
Normal 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
|
18
vlib/v/tests/struct_field_fn_call_test.v
Normal file
18
vlib/v/tests/struct_field_fn_call_test.v
Normal 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
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user