1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

Compare commits

...

15 Commits

Author SHA1 Message Date
shove
65a493d023
v.util: fix a wrong path analysis when parsing 'mod_name' (fix #18970) (#19090) 2023-08-10 05:42:59 +03:00
jacksonmowry
76b4c92848
db.sqlite: make functions return results, breaking change (#19093) 2023-08-10 05:39:32 +03:00
Delyan Angelov
d0cc564089
db.mysql: make mysql.Result.result public (fix #19098) 2023-08-10 05:21:44 +03:00
Alexander Medvednikov
f915366ac4 checker: improve the nil fn error a bit 2023-08-09 22:37:11 +03:00
yuyi
3211a653c3
scanner: fix string interpolation with nested string interpolation in inner quotes 2 (#19094) 2023-08-09 15:05:17 +03:00
Delyan Angelov
eef9b5f168
builtin,os: fix compiling V programs with latest clang 16 on windows (clang 16 is stricter than clang 14) (#19095) 2023-08-09 15:04:44 +03:00
Turiiya
64029a2980
vdoc: implement keyboard shortcuts for search navigation (#19088) 2023-08-09 13:53:15 +03:00
yuyi
b7afe6b236
scanner: add error for invalid newline rune literal, make errors more informative (#19091) 2023-08-09 08:49:47 +03:00
Delyan Angelov
6813a12339
transformer: keep the symbolic expressions inside dump(expr) from being optimised out, even when they could be, when composed of literals known at comptime (#19086) 2023-08-08 18:25:55 +03:00
shove
10df697d32
time: add 'i', 'ii' in custom_format() for 12-hours clock(0-12-1-11) (#19083) 2023-08-08 12:25:39 +03:00
yuyi
68f18fcb8e
scanner: fix string interpolation with nested string interpolation in inner quotes (fix #19081) (#19085) 2023-08-08 12:25:05 +03:00
shove
f4859ffb11
checker: fix missing or_block check for left expr of CallExpr(fix #19061) (#19074) 2023-08-08 09:06:03 +03:00
Turiiya
3b3395d93b
vpm: don't keep empty dirs for git installs (#19070) 2023-08-08 08:59:16 +03:00
Swastik Baranwal
8db1aaafd5
checker: explicitly disallow creating type aliases of none, i.e. type Abc = none (#19078) 2023-08-08 08:58:10 +03:00
Delyan Angelov
286d39706b
time: add a format_rfc3339_nano() method to time.Time 2023-08-08 08:35:05 +03:00
40 changed files with 445 additions and 178 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -131,7 +131,7 @@ pub fn connect(path string) !DB {
code := C.sqlite3_open(&char(path.str), &db)
if code != 0 {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db))) }
code: code
}
}
@ -150,7 +150,7 @@ pub fn (mut db DB) close() !bool {
db.is_open = false
} else {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errmsg(db.conn))) }
code: code
}
}
@ -180,41 +180,50 @@ pub fn (db &DB) get_affected_rows_count() int {
return C.sqlite3_changes(db.conn)
}
// q_int returns a single integer value, from the first column of the result of executing `query`
pub fn (db &DB) q_int(query string) int {
// q_int returns a single integer value, from the first column of the result of executing `query`, or an error on failure
pub fn (db &DB) q_int(query string) !int {
stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
C.sqlite3_step(stmt)
code := C.sqlite3_step(stmt)
if code != sqlite.sqlite_row {
return db.error_message(code, query)
}
res := C.sqlite3_column_int(stmt, 0)
return res
}
// q_string returns a single string value, from the first column of the result of executing `query`
pub fn (db &DB) q_string(query string) string {
// q_string returns a single string value, from the first column of the result of executing `query`, or an error on failure
pub fn (db &DB) q_string(query string) !string {
stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
C.sqlite3_step(stmt)
code := C.sqlite3_step(stmt)
if code != sqlite.sqlite_row {
return db.error_message(code, query)
}
val := unsafe { &u8(C.sqlite3_column_text(stmt, 0)) }
return if val != &u8(0) { unsafe { tos_clone(val) } } else { '' }
}
// exec executes the query on the given `db`, and returns an array of all the results, alongside any result code.
// Result codes: https://www.sqlite.org/rescode.html
// exec executes the query on the given `db`, and returns an array of all the results, or an error on failure
[manualfree]
pub fn (db &DB) exec(query string) ([]Row, int) {
pub fn (db &DB) exec(query string) ![]Row {
stmt := &C.sqlite3_stmt(unsafe { nil })
defer {
C.sqlite3_finalize(stmt)
}
C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), query.len, &stmt, 0)
if code != sqlite.sqlite_ok {
return db.error_message(code, query)
}
nr_cols := C.sqlite3_column_count(stmt)
mut res := 0
mut rows := []Row{}
@ -236,26 +245,21 @@ pub fn (db &DB) exec(query string) ([]Row, int) {
}
rows << row
}
return rows, res
return rows
}
// exec_one executes a query on the given `db`.
// It returns either the first row from the result, if the query was successful, or an error.
[manualfree]
pub fn (db &DB) exec_one(query string) !Row {
rows, code := db.exec(query)
rows := db.exec(query)!
defer {
unsafe { rows.free() }
}
if rows.len == 0 {
return &SQLError{
msg: 'No rows'
code: code
}
} else if code != 101 {
return &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
code: code
code: sqlite.sqlite_done
}
}
res := rows[0]
@ -295,21 +299,13 @@ pub fn (db &DB) exec_param_many(query string, params []string) ![]Row {
mut code := C.sqlite3_prepare_v2(db.conn, &char(query.str), -1, &stmt, 0)
if code != 0 {
return &SQLError{
msg: unsafe {
cstring_to_vstring(&char(C.sqlite3_errstr(code)))
}
code: code
}
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 &SQLError{
msg: unsafe { cstring_to_vstring(&char(C.sqlite3_errstr(code))) }
code: code
}
return db.error_message(code, query)
}
}
@ -345,8 +341,8 @@ pub fn (db &DB) exec_param(query string, param string) ![]Row {
// 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.
@ -359,37 +355,39 @@ pub fn (db &DB) busy_timeout(ms int) int {
// synchronization_mode sets disk synchronization mode, which controls how
// aggressively SQLite will write data to physical storage.
// If the command fails to execute an error is returned
// .off: No syncs at all. (fastest)
// .normal: Sync after each sequence of critical disk operations.
// .full: Sync after each critical disk operation (slowest).
pub fn (db &DB) synchronization_mode(sync_mode SyncMode) {
pub fn (db &DB) synchronization_mode(sync_mode SyncMode) ! {
if sync_mode == .off {
db.exec('pragma synchronous = OFF;')
db.exec('pragma synchronous = OFF;')!
} else if sync_mode == .full {
db.exec('pragma synchronous = FULL;')
db.exec('pragma synchronous = FULL;')!
} else {
db.exec('pragma synchronous = NORMAL;')
db.exec('pragma synchronous = NORMAL;')!
}
}
// journal_mode controls how the journal file is stored and processed.
// If the command fails to execute an error is returned
// .off: No journal record is kept. (fastest)
// .memory: Journal record is held in memory, rather than on disk.
// .delete: At the conclusion of a transaction, journal file is deleted.
// .truncate: Journal file is truncated to a length of zero bytes.
// .persist: Journal file is left in place, but the header is overwritten to indicate journal is no longer valid.
pub fn (db &DB) journal_mode(journal_mode JournalMode) {
pub fn (db &DB) journal_mode(journal_mode JournalMode) ! {
if journal_mode == .off {
db.exec('pragma journal_mode = OFF;')
db.exec('pragma journal_mode = OFF;')!
} else if journal_mode == .delete {
db.exec('pragma journal_mode = DELETE;')
db.exec('pragma journal_mode = DELETE;')!
} else if journal_mode == .truncate {
db.exec('pragma journal_mode = TRUNCATE;')
db.exec('pragma journal_mode = TRUNCATE;')!
} else if journal_mode == .persist {
db.exec('pragma journal_mode = PERSIST;')
db.exec('pragma journal_mode = PERSIST;')!
} else if journal_mode == .memory {
db.exec('pragma journal_mode = MEMORY;')
db.exec('pragma journal_mode = MEMORY;')!
} else {
db.exec('pragma journal_mode = MEMORY;')
db.exec('pragma journal_mode = MEMORY;')!
}
}

View File

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

View File

@ -35,49 +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
db.exec_param('insert into users (name) values (?)', 'Tom')!
assert db.last_insert_rowid() == 4
nr_users := db.q_int('select count(*) from users')
nr_users := db.q_int('select count(*) from users')!
assert nr_users == 4
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'
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')
users := db.exec('select * from users')!
assert users.len == 4
assert code == 101
code = db.exec_none('vacuum')
code := db.exec_none('vacuum')
assert code == 101
user := db.exec_one('select * from users where id = 3') or { panic(err) }
println(user)
assert user.vals.len == 2
db.exec("update users set name='zzzz' where name='qqqq'")
db.exec("update users set name='zzzz' where name='qqqq'")!
assert db.get_affected_rows_count() == 0
db.exec("update users set name='Peter1' where name='Peter'")
db.exec("update users set name='Peter1' where name='Peter'")!
assert db.get_affected_rows_count() == 1
db.exec_param_many('update users set name=? where name=?', ['Peter', 'Peter1'])!
assert db.get_affected_rows_count() == 1
db.exec("delete from users where name='qqqq'")
db.exec("delete from users where name='qqqq'")!
assert db.get_affected_rows_count() == 0
db.exec("delete from users where name='Sam'")
db.exec("delete from users where name='Sam'")!
assert db.get_affected_rows_count() == 1
db.close() or { panic(err) }

View File

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

View File

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

View File

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

View File

@ -5,34 +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('hh A h a') == '00 AM 0 am'
assert time.parse('2023-08-04 01:00:45')!.custom_format('hh A h a') == '01 AM 1 am'
assert time.parse('2023-08-04 02:00:45')!.custom_format('hh A h a') == '02 AM 2 am'
assert time.parse('2023-08-04 03:00:45')!.custom_format('hh A h a') == '03 AM 3 am'
assert time.parse('2023-08-04 04:00:45')!.custom_format('hh A h a') == '04 AM 4 am'
assert time.parse('2023-08-04 05:00:45')!.custom_format('hh A h a') == '05 AM 5 am'
assert time.parse('2023-08-04 06:00:45')!.custom_format('hh A h a') == '06 AM 6 am'
assert time.parse('2023-08-04 07:00:45')!.custom_format('hh A h a') == '07 AM 7 am'
assert time.parse('2023-08-04 08:00:45')!.custom_format('hh A h a') == '08 AM 8 am'
assert time.parse('2023-08-04 09:00:45')!.custom_format('hh A h a') == '09 AM 9 am'
assert time.parse('2023-08-04 10:00:45')!.custom_format('hh A h a') == '10 AM 10 am'
assert time.parse('2023-08-04 11:00:45')!.custom_format('hh A h a') == '11 AM 11 am'
assert time.parse('2023-08-04 12:00:45')!.custom_format('hh A h a') == '12 PM 12 pm'
assert time.parse('2023-08-04 13:00:45')!.custom_format('hh A h a') == '01 PM 1 pm'
assert time.parse('2023-08-04 14:00:45')!.custom_format('hh A h a') == '02 PM 2 pm'
assert time.parse('2023-08-04 15:00:45')!.custom_format('hh A h a') == '03 PM 3 pm'
assert time.parse('2023-08-04 16:00:45')!.custom_format('hh A h a') == '04 PM 4 pm'
assert time.parse('2023-08-04 17:00:45')!.custom_format('hh A h a') == '05 PM 5 pm'
assert time.parse('2023-08-04 18:00:45')!.custom_format('hh A h a') == '06 PM 6 pm'
assert time.parse('2023-08-04 19:00:45')!.custom_format('hh A h a') == '07 PM 7 pm'
assert time.parse('2023-08-04 20:00:45')!.custom_format('hh A h a') == '08 PM 8 pm'
assert time.parse('2023-08-04 21:00:45')!.custom_format('hh A h a') == '09 PM 9 pm'
assert time.parse('2023-08-04 22:00:45')!.custom_format('hh A h a') == '10 PM 10 pm'
assert time.parse('2023-08-04 23:00:45')!.custom_format('hh A h a') == '11 PM 11 pm'
assert time.parse('2023-08-04 00:00:45')!.custom_format('ii A i a hh A h a') == '00 AM 0 am 12 AM 12 am'
assert time.parse('2023-08-04 01:00:45')!.custom_format('ii A i a hh A h a') == '01 AM 1 am 01 AM 1 am'
assert time.parse('2023-08-04 02:00:45')!.custom_format('ii A i a hh A h a') == '02 AM 2 am 02 AM 2 am'
assert time.parse('2023-08-04 03:00:45')!.custom_format('ii A i a hh A h a') == '03 AM 3 am 03 AM 3 am'
assert time.parse('2023-08-04 04:00:45')!.custom_format('ii A i a hh A h a') == '04 AM 4 am 04 AM 4 am'
assert time.parse('2023-08-04 05:00:45')!.custom_format('ii A i a hh A h a') == '05 AM 5 am 05 AM 5 am'
assert time.parse('2023-08-04 06:00:45')!.custom_format('ii A i a hh A h a') == '06 AM 6 am 06 AM 6 am'
assert time.parse('2023-08-04 07:00:45')!.custom_format('ii A i a hh A h a') == '07 AM 7 am 07 AM 7 am'
assert time.parse('2023-08-04 08:00:45')!.custom_format('ii A i a hh A h a') == '08 AM 8 am 08 AM 8 am'
assert time.parse('2023-08-04 09:00:45')!.custom_format('ii A i a hh A h a') == '09 AM 9 am 09 AM 9 am'
assert time.parse('2023-08-04 10:00:45')!.custom_format('ii A i a hh A h a') == '10 AM 10 am 10 AM 10 am'
assert time.parse('2023-08-04 11:00:45')!.custom_format('ii A i a hh A h a') == '11 AM 11 am 11 AM 11 am'
assert time.parse('2023-08-04 12:00:45')!.custom_format('ii A i a hh A h a') == '12 PM 12 pm 12 PM 12 pm'
assert time.parse('2023-08-04 13:00:45')!.custom_format('ii A i a hh A h a') == '01 PM 1 pm 01 PM 1 pm'
assert time.parse('2023-08-04 14:00:45')!.custom_format('ii A i a hh A h a') == '02 PM 2 pm 02 PM 2 pm'
assert time.parse('2023-08-04 15:00:45')!.custom_format('ii A i a hh A h a') == '03 PM 3 pm 03 PM 3 pm'
assert time.parse('2023-08-04 16:00:45')!.custom_format('ii A i a hh A h a') == '04 PM 4 pm 04 PM 4 pm'
assert time.parse('2023-08-04 17:00:45')!.custom_format('ii A i a hh A h a') == '05 PM 5 pm 05 PM 5 pm'
assert time.parse('2023-08-04 18:00:45')!.custom_format('ii A i a hh A h a') == '06 PM 6 pm 06 PM 6 pm'
assert time.parse('2023-08-04 19:00:45')!.custom_format('ii A i a hh A h a') == '07 PM 7 pm 07 PM 7 pm'
assert time.parse('2023-08-04 20:00:45')!.custom_format('ii A i a hh A h a') == '08 PM 8 pm 08 PM 8 pm'
assert time.parse('2023-08-04 21:00:45')!.custom_format('ii A i a hh A h a') == '09 PM 9 pm 09 PM 9 pm'
assert time.parse('2023-08-04 22:00:45')!.custom_format('ii A i a hh A h a') == '10 PM 10 pm 10 PM 10 pm'
assert time.parse('2023-08-04 23:00:45')!.custom_format('ii A i a hh A h a') == '11 PM 11 pm 11 PM 11 pm'
}

View File

@ -20,14 +20,6 @@ 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.nanosecond / 1_000_000):03d}'
}
// 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)
// RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar.
// 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.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.nanosecond / 1_000):06d}'
@ -38,6 +30,20 @@ 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)
// RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar.
// 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.nanosecond / 1_000_000):03d}Z'
}
// 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).
pub fn (t Time) hhmm() string {
return '${t.hour:02d}:${t.minute:02d}'
@ -90,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']
)
@ -132,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 |
@ -226,10 +234,18 @@ pub fn (t Time) custom_format(s string) string {
sb.write_string('${t.hour:02}')
}
'h' {
h := if t.hour > 12 { t.hour - 12 } else { t.hour }
h := (t.hour + 11) % 12 + 1
sb.write_string(h.str())
}
'hh' {
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}')
}

View File

@ -89,6 +89,13 @@ fn test_format_rfc3339() {
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)
}

View File

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

View File

@ -490,6 +490,9 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type {
c.error('unknown function: ${node.name}', node.pos)
}
}
// If the left expr has an or_block, it needs to be checked for legal or_block statement.
return_type := c.expr(mut node.left)
c.check_expr_opt_call(node.left, return_type)
// TODO merge logic from method_call and fn_call
// First check everything that applies to both fns and methods
old_inside_fn_arg := c.inside_fn_arg

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -152,7 +152,7 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri
}
if last_v_mod > -1 {
mod_full_name := try_path_parts[last_v_mod..].join('.')
return mod_full_name
return if mod_full_name.len < mod.len { mod } else { mod_full_name }
}
}
}