hot code reloading examples
parent
af19aa5096
commit
de8dc4cddb
|
@ -115,6 +115,10 @@ fn new_fn(pkg string, is_public bool) *Fn {
|
||||||
fn (p mut Parser) fn_decl() {
|
fn (p mut Parser) fn_decl() {
|
||||||
p.fgen('fn ')
|
p.fgen('fn ')
|
||||||
is_pub := p.tok == PUB
|
is_pub := p.tok == PUB
|
||||||
|
is_live := p.attr == 'live' && !p.pref.is_so
|
||||||
|
if is_live && !p.pref.is_live {
|
||||||
|
p.error('run `v -live program.v` if you want to use [live] functions')
|
||||||
|
}
|
||||||
if is_pub {
|
if is_pub {
|
||||||
p.next()
|
p.next()
|
||||||
}
|
}
|
||||||
|
@ -277,16 +281,12 @@ fn (p mut Parser) fn_decl() {
|
||||||
// }
|
// }
|
||||||
mut fn_name_cgen := p.table.cgen_name(f)
|
mut fn_name_cgen := p.table.cgen_name(f)
|
||||||
// Start generation of the function body
|
// Start generation of the function body
|
||||||
is_live := p.pref.is_live && f.name != 'main' && f.name != 'reload_so'
|
|
||||||
skip_main_in_test := f.name == 'main' && p.pref.is_test
|
skip_main_in_test := f.name == 'main' && p.pref.is_test
|
||||||
if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test {
|
if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test {
|
||||||
if p.pref.obfuscate {
|
if p.pref.obfuscate {
|
||||||
p.genln('; // ${f.name}')
|
p.genln('; // $f.name')
|
||||||
}
|
}
|
||||||
p.genln('$typ $fn_name_cgen($str_args) {')
|
p.genln('$typ $fn_name_cgen($str_args) {')
|
||||||
// if f.name == 'WinMain' {
|
|
||||||
// typ = 'int'
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
if is_fn_header {
|
if is_fn_header {
|
||||||
p.genln('$typ $fn_name_cgen($str_args);')
|
p.genln('$typ $fn_name_cgen($str_args);')
|
||||||
|
@ -328,8 +328,8 @@ fn (p mut Parser) fn_decl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Live code reloading? Load all fns from .so
|
// Live code reloading? Load all fns from .so
|
||||||
if is_live && p.first_run() {
|
if is_live && p.first_run() && p.pkg == 'main' {
|
||||||
// p.cgen.consts_init.push('$fn_name_cgen = dlsym(lib, "$fn_name_cgen");')
|
//println('ADDING SO FN $fn_name_cgen')
|
||||||
p.cgen.so_fns << fn_name_cgen
|
p.cgen.so_fns << fn_name_cgen
|
||||||
fn_name_cgen = '(* $fn_name_cgen )'
|
fn_name_cgen = '(* $fn_name_cgen )'
|
||||||
}
|
}
|
||||||
|
@ -361,8 +361,10 @@ fn (p mut Parser) fn_decl() {
|
||||||
}
|
}
|
||||||
// We are in live code reload mode, call the .so loader in bg
|
// We are in live code reload mode, call the .so loader in bg
|
||||||
if p.pref.is_live {
|
if p.pref.is_live {
|
||||||
|
file_base := p.file_path.replace('.v', '')
|
||||||
|
so_name := file_base + '.so'
|
||||||
p.genln('
|
p.genln('
|
||||||
load_so("bounce.so");
|
load_so("$so_name");
|
||||||
pthread_t _thread_so;
|
pthread_t _thread_so;
|
||||||
pthread_create(&_thread_so , NULL, &reload_so, NULL); ')
|
pthread_create(&_thread_so , NULL, &reload_so, NULL); ')
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,16 +374,47 @@ string _STR_TMP(const char *fmt, ...) {
|
||||||
cgen.genln('return g_test_ok == 0; }')
|
cgen.genln('return g_test_ok == 0; }')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Hot code reloading
|
||||||
if v.pref.is_live {
|
if v.pref.is_live {
|
||||||
cgen.genln(' int load_so(byteptr path) {
|
file := v.dir
|
||||||
printf("load_so %s\\n", path); dlclose(live_lib); live_lib = dlopen(path, RTLD_LAZY);
|
file_base := v.dir.replace('.v', '')
|
||||||
if (!live_lib) {puts("open failed"); exit(1); return 0;}
|
so_name := file_base + '.so'
|
||||||
')
|
// Need to build .so file before building the live application
|
||||||
|
// The live app needs to load this .so file on initialization.
|
||||||
|
os.system('v -o $file_base -shared $file')
|
||||||
|
cgen.genln('
|
||||||
|
#include <dlfcn.h>
|
||||||
|
void* live_lib;
|
||||||
|
int load_so(byteptr path) {
|
||||||
|
//printf("load_so %s\\n", path);
|
||||||
|
if (live_lib) dlclose(live_lib);
|
||||||
|
live_lib = dlopen(path, RTLD_LAZY);
|
||||||
|
if (!live_lib) {puts("open failed"); exit(1); return 0;}
|
||||||
|
')
|
||||||
for so_fn in cgen.so_fns {
|
for so_fn in cgen.so_fns {
|
||||||
cgen.genln('$so_fn = dlsym(live_lib, "$so_fn"); ')
|
cgen.genln('$so_fn = dlsym(live_lib, "$so_fn"); ')
|
||||||
}
|
}
|
||||||
cgen.genln('return 1; }')
|
cgen.genln('return 1; }
|
||||||
|
|
||||||
|
void reload_so() {
|
||||||
|
int last = 0;
|
||||||
|
while (1) {
|
||||||
|
int now = os__file_last_mod_unix(tos2("$file"));
|
||||||
|
if (now != last) {
|
||||||
|
//v -o bounce -shared bounce.v
|
||||||
|
os__system(tos2("v -o $file_base -shared $file"));
|
||||||
|
last = now;
|
||||||
|
load_so("$so_name");
|
||||||
|
}
|
||||||
|
time__sleep_ms(1000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
' )
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.pref.is_so {
|
||||||
|
cgen.genln(' int load_so(byteptr path) { return 0; }')
|
||||||
|
}
|
||||||
cgen.save()
|
cgen.save()
|
||||||
if v.pref.is_verbose {
|
if v.pref.is_verbose {
|
||||||
v.log('flags=')
|
v.log('flags=')
|
||||||
|
@ -432,13 +463,13 @@ fn (c &V) cc_windows_cross() {
|
||||||
}
|
}
|
||||||
mut libs := ''
|
mut libs := ''
|
||||||
if c.pref.build_mode == .default_mode {
|
if c.pref.build_mode == .default_mode {
|
||||||
libs = '"$TmpPath/vlib/builtin.o"'
|
libs = '$TmpPath/vlib/builtin.o'
|
||||||
if !os.file_exists(libs) {
|
if !os.file_exists(libs) {
|
||||||
println('`builtin.o` not found')
|
println('`builtin.o` not found')
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
for imp in c.table.imports {
|
for imp in c.table.imports {
|
||||||
libs += ' "$TmpPath/vlib/${imp}.o"'
|
libs += ' $TmpPath/vlib/${imp}.o'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args += ' $c.out_name_c '
|
args += ' $c.out_name_c '
|
||||||
|
@ -500,13 +531,11 @@ fn (v mut V) cc() {
|
||||||
v.log('cc() isprod=$v.pref.is_prod outname=$v.out_name')
|
v.log('cc() isprod=$v.pref.is_prod outname=$v.out_name')
|
||||||
mut a := ['-w', '-march=native']// arguments for the C compiler
|
mut a := ['-w', '-march=native']// arguments for the C compiler
|
||||||
flags := v.table.flags.join(' ')
|
flags := v.table.flags.join(' ')
|
||||||
/*
|
//mut shared := ''
|
||||||
mut shared := ''
|
|
||||||
if v.pref.is_so {
|
if v.pref.is_so {
|
||||||
a << '-shared'// -Wl,-z,defs'
|
a << '-shared'// -Wl,-z,defs'
|
||||||
v.out_name = v.out_name + '.so'
|
v.out_name = v.out_name + '.so'
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
if v.pref.is_prod {
|
if v.pref.is_prod {
|
||||||
a << '-O2'
|
a << '-O2'
|
||||||
}
|
}
|
||||||
|
@ -521,7 +550,7 @@ fn (v mut V) cc() {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
else if v.pref.build_mode == .default_mode {
|
else if v.pref.build_mode == .default_mode {
|
||||||
libs = '"$TmpPath/vlib/builtin.o"'
|
libs = '$TmpPath/vlib/builtin.o'
|
||||||
if !os.file_exists(libs) {
|
if !os.file_exists(libs) {
|
||||||
println('`builtin.o` not found')
|
println('`builtin.o` not found')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -530,7 +559,7 @@ fn (v mut V) cc() {
|
||||||
if imp == 'webview' {
|
if imp == 'webview' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
libs += ' "$TmpPath/vlib/${imp}.o"'
|
libs += ' $TmpPath/vlib/${imp}.o'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// -I flags
|
// -I flags
|
||||||
|
@ -562,7 +591,7 @@ mut args := ''
|
||||||
// else {
|
// else {
|
||||||
a << '-o $v.out_name'
|
a << '-o $v.out_name'
|
||||||
// The C file we are compiling
|
// The C file we are compiling
|
||||||
a << '"$TmpPath/$v.out_name_c"'
|
a << '$TmpPath/$v.out_name_c'
|
||||||
// }
|
// }
|
||||||
// Min macos version is mandatory I think?
|
// Min macos version is mandatory I think?
|
||||||
if v.os == MAC {
|
if v.os == MAC {
|
||||||
|
@ -717,7 +746,7 @@ fn (v mut V) add_user_v_files() {
|
||||||
// Parse lib imports
|
// Parse lib imports
|
||||||
if v.pref.build_mode == .default_mode {
|
if v.pref.build_mode == .default_mode {
|
||||||
for i := 0; i < v.table.imports.len; i++ {
|
for i := 0; i < v.table.imports.len; i++ {
|
||||||
pkg := v.module_path(v.table.imports[i])
|
pkg := v.table.imports[i]
|
||||||
vfiles := v.v_files_from_dir('$TmpPath/vlib/$pkg')
|
vfiles := v.v_files_from_dir('$TmpPath/vlib/$pkg')
|
||||||
// Add all imports referenced by these libs
|
// Add all imports referenced by these libs
|
||||||
for file in vfiles {
|
for file in vfiles {
|
||||||
|
@ -730,7 +759,7 @@ fn (v mut V) add_user_v_files() {
|
||||||
// TODO this used to crash compiler?
|
// TODO this used to crash compiler?
|
||||||
// for pkg in v.table.imports {
|
// for pkg in v.table.imports {
|
||||||
for i := 0; i < v.table.imports.len; i++ {
|
for i := 0; i < v.table.imports.len; i++ {
|
||||||
pkg := v.module_path(v.table.imports[i])
|
pkg := v.table.imports[i]
|
||||||
idir := os.getwd()
|
idir := os.getwd()
|
||||||
mut import_path := '$idir/$pkg'
|
mut import_path := '$idir/$pkg'
|
||||||
if(!os.file_exists(import_path)) {
|
if(!os.file_exists(import_path)) {
|
||||||
|
@ -749,8 +778,7 @@ fn (v mut V) add_user_v_files() {
|
||||||
println(v.table.imports)
|
println(v.table.imports)
|
||||||
}
|
}
|
||||||
// Only now add all combined lib files
|
// Only now add all combined lib files
|
||||||
for _pkg in v.table.imports {
|
for pkg in v.table.imports {
|
||||||
pkg := v.module_path(_pkg)
|
|
||||||
idir := os.getwd()
|
idir := os.getwd()
|
||||||
mut module_path := '$idir/$pkg'
|
mut module_path := '$idir/$pkg'
|
||||||
// If we are in default mode, we don't parse vlib .v files, but header .vh files in
|
// If we are in default mode, we don't parse vlib .v files, but header .vh files in
|
||||||
|
@ -791,15 +819,6 @@ fn get_arg(joined_args, arg, def string) string {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (v &V) module_path(pkg string) string {
|
|
||||||
// submodule support
|
|
||||||
if pkg.contains('.') {
|
|
||||||
// return pkg.replace('.', path_sep)
|
|
||||||
return pkg.replace('.', '/')
|
|
||||||
}
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (v &V) log(s string) {
|
fn (v &V) log(s string) {
|
||||||
if !v.pref.is_verbose {
|
if !v.pref.is_verbose {
|
||||||
return
|
return
|
||||||
|
|
|
@ -63,6 +63,7 @@ mut:
|
||||||
vroot string
|
vroot string
|
||||||
is_c_struct_init bool
|
is_c_struct_init bool
|
||||||
can_chash bool
|
can_chash bool
|
||||||
|
attr string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -179,7 +180,11 @@ fn (p mut Parser) parse() {
|
||||||
p.fn_decl()
|
p.fn_decl()
|
||||||
case TIP:
|
case TIP:
|
||||||
p.type_decl()
|
p.type_decl()
|
||||||
case STRUCT, INTERFACE, UNION, LSBR:// `[` can only mean an [attribute] before the struct definition
|
case LSBR:
|
||||||
|
// `[` can only mean an [attribute] before a function
|
||||||
|
// or a struct definition
|
||||||
|
p.attribute()
|
||||||
|
case STRUCT, INTERFACE, UNION, LSBR:
|
||||||
p.struct_decl()
|
p.struct_decl()
|
||||||
case CONST:
|
case CONST:
|
||||||
p.const_decl()
|
p.const_decl()
|
||||||
|
@ -192,8 +197,8 @@ fn (p mut Parser) parse() {
|
||||||
// $if, $else
|
// $if, $else
|
||||||
p.comp_time()
|
p.comp_time()
|
||||||
case GLOBAL:
|
case GLOBAL:
|
||||||
if !p.pref.translated && !p.builtin_pkg && !p.building_v() {
|
if !p.pref.translated && !p.pref.is_live && !p.builtin_pkg && !p.building_v() {
|
||||||
p.error('__global is only allowed in translated code')
|
//p.error('__global is only allowed in translated code')
|
||||||
}
|
}
|
||||||
p.next()
|
p.next()
|
||||||
name := p.check_name()
|
name := p.check_name()
|
||||||
|
@ -289,21 +294,7 @@ fn (p mut Parser) import_statement() {
|
||||||
if p.tok != NAME {
|
if p.tok != NAME {
|
||||||
p.error('bad import format')
|
p.error('bad import format')
|
||||||
}
|
}
|
||||||
mut pkg := p.lit.trim_space()
|
pkg := p.lit.trim_space()
|
||||||
// submodule support
|
|
||||||
// limit depth to 4 for now
|
|
||||||
max_module_depth := 4
|
|
||||||
mut depth := 1
|
|
||||||
for p.peek() == DOT {
|
|
||||||
p.next() // SKIP DOT
|
|
||||||
p.next() // SUBMODULE
|
|
||||||
submodule := p.lit.trim_space()
|
|
||||||
pkg = pkg + '.' + submodule
|
|
||||||
depth++
|
|
||||||
if depth > max_module_depth {
|
|
||||||
panic('Sorry. Module depth of $max_module_depth exceeded: $pkg ($submodule is too deep).')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.next()
|
p.next()
|
||||||
p.fgenln(' ' + pkg)
|
p.fgenln(' ' + pkg)
|
||||||
// Make sure there are no duplicate imports
|
// Make sure there are no duplicate imports
|
||||||
|
@ -1142,13 +1133,6 @@ fn (p mut Parser) var_decl() {
|
||||||
p.next()
|
p.next()
|
||||||
p.check(LCBR)
|
p.check(LCBR)
|
||||||
p.genln('if (!$tmp .ok) {')
|
p.genln('if (!$tmp .ok) {')
|
||||||
p.register_var(Var {
|
|
||||||
name: 'err'
|
|
||||||
typ: 'string'
|
|
||||||
is_mut: false
|
|
||||||
is_used: true
|
|
||||||
})
|
|
||||||
p.genln('string err = $tmp . error;')
|
|
||||||
p.statements()
|
p.statements()
|
||||||
p.genln('$typ $name = *($typ*) $tmp . data;')
|
p.genln('$typ $name = *($typ*) $tmp . data;')
|
||||||
if !p.returns && p.prev_tok2 != CONTINUE && p.prev_tok2 != BREAK {
|
if !p.returns && p.prev_tok2 != CONTINUE && p.prev_tok2 != BREAK {
|
||||||
|
@ -2658,13 +2642,6 @@ fn (p mut Parser) chash() {
|
||||||
if is_sig {
|
if is_sig {
|
||||||
// p.cgen.nogen = true
|
// p.cgen.nogen = true
|
||||||
}
|
}
|
||||||
if hash == 'live' {
|
|
||||||
if p.pref.is_so {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.pref.is_live = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if hash.starts_with('flag ') {
|
if hash.starts_with('flag ') {
|
||||||
mut flag := hash.right(5)
|
mut flag := hash.right(5)
|
||||||
// No the right os? Skip!
|
// No the right os? Skip!
|
||||||
|
@ -3184,6 +3161,23 @@ fn (p &Parser) building_v() bool {
|
||||||
return p.file_path.contains('v/compiler') || cur_dir.contains('v/compiler')
|
return p.file_path.contains('v/compiler') || cur_dir.contains('v/compiler')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (p mut Parser) attribute() {
|
||||||
|
p.check(LSBR)
|
||||||
|
p.attr = p.check_name()
|
||||||
|
p.check(RSBR)
|
||||||
|
if p.tok == FUNC {
|
||||||
|
p.fn_decl()
|
||||||
|
p.attr = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if p.tok == STRUCT {
|
||||||
|
p.struct_decl()
|
||||||
|
p.attr = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.error('bad attribute usage')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
|
@ -626,7 +626,10 @@ fn (s mut Scanner) ident_char() string {
|
||||||
len--
|
len--
|
||||||
c := s.text.substr(start + 1, s.pos)
|
c := s.text.substr(start + 1, s.pos)
|
||||||
if len != 1 {
|
if len != 1 {
|
||||||
|
u := c.ustring()
|
||||||
|
if u.len != 1 {
|
||||||
s.error('invalid character literal (more than one character: $len)')
|
s.error('invalid character literal (more than one character: $len)')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Build this example with
|
||||||
|
// v -live bounce.v
|
||||||
|
module main
|
||||||
|
|
||||||
|
import gx
|
||||||
|
import gl
|
||||||
|
import gg
|
||||||
|
import glfw
|
||||||
|
import time
|
||||||
|
|
||||||
|
struct Game {
|
||||||
|
mut:
|
||||||
|
vg *gg.GG
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
dy int
|
||||||
|
dx int
|
||||||
|
height int
|
||||||
|
width int
|
||||||
|
main_wnd *glfw.Window
|
||||||
|
draw_fn voidptr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
glfw.init()
|
||||||
|
width := 600
|
||||||
|
height := 300
|
||||||
|
mut game := &Game{
|
||||||
|
vg: 0
|
||||||
|
dx: 3
|
||||||
|
dy: 3
|
||||||
|
height: height
|
||||||
|
width: width
|
||||||
|
}
|
||||||
|
mut window := glfw.create_window(glfw.WinCfg {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
borderless: false,
|
||||||
|
title: 'Hot code reloading demo'
|
||||||
|
ptr: game
|
||||||
|
})
|
||||||
|
//window.onkeydown(key_down)
|
||||||
|
game.main_wnd = window
|
||||||
|
window.make_context_current()
|
||||||
|
gg.init()
|
||||||
|
game.vg = gg.new_context(gg.Cfg {
|
||||||
|
width: width
|
||||||
|
height: height
|
||||||
|
font_size: 20
|
||||||
|
use_ortho: true
|
||||||
|
})
|
||||||
|
println('Starting game loop...')
|
||||||
|
go game.run()
|
||||||
|
for {
|
||||||
|
gl.clear()
|
||||||
|
gl.clear_color(255, 255, 255, 255)
|
||||||
|
game.draw()
|
||||||
|
window.swap_buffers()
|
||||||
|
glfw.wait_events()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
W = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
//[live] TODO segfaults
|
||||||
|
fn (ctx &Game) draw() {
|
||||||
|
ctx.vg.draw_rect(ctx.x, ctx.y, W, W, gx.rgb(0, 0, 255))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (ctx mut Game) run() {
|
||||||
|
for {
|
||||||
|
ctx.x += ctx.dx
|
||||||
|
ctx.y += ctx.dy
|
||||||
|
if ctx.y >= ctx.height - W || ctx.y <= 0 {
|
||||||
|
ctx.dy = - ctx.dy
|
||||||
|
}
|
||||||
|
if ctx.x >= ctx.width - W || ctx.x <= 0 {
|
||||||
|
ctx.dx = - ctx.dx
|
||||||
|
}
|
||||||
|
// Refresh
|
||||||
|
time.sleep_ms(30)
|
||||||
|
glfw.post_empty_event()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Build this example with
|
||||||
|
// v -live message.v
|
||||||
|
module main
|
||||||
|
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
[live]
|
||||||
|
fn print_message() {
|
||||||
|
println('Hello! Modify this message while the program is running.')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
for {
|
||||||
|
print_message()
|
||||||
|
time.sleep_ms(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -641,7 +641,7 @@ pub fn (s string) ustring_tmp() ustring {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (u ustring) substr(start, end int) string {
|
pub fn (u ustring) substr(start, end int) string {
|
||||||
start = u.runes[start]
|
start = u.runes[start]
|
||||||
if end >= u.runes.len {
|
if end >= u.runes.len {
|
||||||
end = u.s.len
|
end = u.s.len
|
||||||
|
@ -652,11 +652,11 @@ fn (u ustring) substr(start, end int) string {
|
||||||
return u.s.substr(start, end)
|
return u.s.substr(start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (u ustring) left(pos int) string {
|
pub fn (u ustring) left(pos int) string {
|
||||||
return u.substr(0, pos)
|
return u.substr(0, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (u ustring) right(pos int) string {
|
pub fn (u ustring) right(pos int) string {
|
||||||
return u.substr(pos, u.len)
|
return u.substr(pos, u.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
vlib/os/os.v
11
vlib/os/os.v
|
@ -66,6 +66,7 @@ import const (
|
||||||
struct C.stat {
|
struct C.stat {
|
||||||
st_size int
|
st_size int
|
||||||
st_mode int
|
st_mode int
|
||||||
|
st_mtime int
|
||||||
}
|
}
|
||||||
|
|
||||||
struct C.DIR {
|
struct C.DIR {
|
||||||
|
@ -640,6 +641,16 @@ pub fn signal(signum int, handler voidptr) {
|
||||||
C.signal(signum, handler)
|
C.signal(signum, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn file_last_mod_unix(path string) int {
|
||||||
|
attr := C.stat{}
|
||||||
|
//# struct stat attr;
|
||||||
|
C.stat(path.str, &attr)
|
||||||
|
//# stat(path.str, &attr);
|
||||||
|
return attr.st_mtime
|
||||||
|
//# return attr.st_mtime ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn log(s string) {
|
fn log(s string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ struct C.PGResult { }
|
||||||
|
|
||||||
fn C.PQconnectdb(a byteptr) *C.PGconn
|
fn C.PQconnectdb(a byteptr) *C.PGconn
|
||||||
fn C.PQerrorMessage(voidptr) byteptr
|
fn C.PQerrorMessage(voidptr) byteptr
|
||||||
|
fn C.PQgetvalue(voidptr, int, int) byteptr
|
||||||
|
|
||||||
pub fn connect(dbname, user string) DB {
|
pub fn connect(dbname, user string) DB {
|
||||||
conninfo := 'host=localhost user=$user dbname=$dbname'
|
conninfo := 'host=localhost user=$user dbname=$dbname'
|
||||||
|
|
Loading…
Reference in New Issue