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

generics, vweb, comptime codegen, etc

This commit is contained in:
Alexander Medvednikov 2019-07-29 18:21:36 +02:00
parent f1373874ef
commit 207bab5f79
20 changed files with 982 additions and 442 deletions

View File

@ -20,11 +20,11 @@ struct CGen {
fns []string fns []string
so_fns []string so_fns []string
consts_init []string consts_init []string
lines []string
//buf strings.Builder //buf strings.Builder
is_user bool is_user bool
mut: mut:
run Pass lines []string
pass Pass
nogen bool nogen bool
tmp_line string tmp_line string
cur_line string cur_line string
@ -53,7 +53,7 @@ fn new_cgen(out_name_c string) *CGen {
} }
fn (g mut CGen) genln(s string) { fn (g mut CGen) genln(s string) {
if g.nogen || g.run != .main { if g.nogen || g.pass != .main {
return return
} }
if g.is_tmp { if g.is_tmp {
@ -72,7 +72,7 @@ fn (g mut CGen) genln(s string) {
} }
fn (g mut CGen) gen(s string) { fn (g mut CGen) gen(s string) {
if g.nogen || g.run != .main { if g.nogen || g.pass != .main {
return return
} }
if g.is_tmp { if g.is_tmp {
@ -84,7 +84,7 @@ fn (g mut CGen) gen(s string) {
} }
fn (g mut CGen) resetln(s string) { fn (g mut CGen) resetln(s string) {
if g.nogen || g.run != .main { if g.nogen || g.pass != .main {
return return
} }
if g.is_tmp { if g.is_tmp {
@ -127,7 +127,7 @@ fn (g mut CGen) add_placeholder() int {
} }
fn (g mut CGen) set_placeholder(pos int, val string) { fn (g mut CGen) set_placeholder(pos int, val string) {
if g.nogen || g.run != .main { if g.nogen || g.pass != .main {
return return
} }
// g.lines.set(pos, val) // g.lines.set(pos, val)
@ -153,7 +153,7 @@ fn (g mut CGen) add_placeholder2() int {
} }
fn (g mut CGen) set_placeholder2(pos int, val string) { fn (g mut CGen) set_placeholder2(pos int, val string) {
if g.nogen || g.run != .main { if g.nogen || g.pass != .main {
return return
} }
if g.is_tmp { if g.is_tmp {
@ -219,21 +219,21 @@ fn (p mut Parser) print_prof_counters() string {
} }
fn (p mut Parser) gen_type(s string) { fn (p mut Parser) gen_type(s string) {
if !p.first_run() { if !p.first_pass() {
return return
} }
p.cgen.types << s p.cgen.types << s
} }
fn (p mut Parser) gen_typedef(s string) { fn (p mut Parser) gen_typedef(s string) {
if !p.first_run() { if !p.first_pass() {
return return
} }
p.cgen.typedefs << s p.cgen.typedefs << s
} }
fn (p mut Parser) gen_type_alias(s string) { fn (p mut Parser) gen_type_alias(s string) {
if !p.first_run() { if !p.first_pass() {
return return
} }
p.cgen.type_aliases << s p.cgen.type_aliases << s

203
compiler/comptime.v Normal file
View File

@ -0,0 +1,203 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import (
vweb.tmpl // for `$vweb_html()`
os
)
fn (p mut Parser) comp_time() {
p.check(.dollar)
if p.tok == .key_if {
p.check(.key_if)
p.fspace()
not := p.tok == .not
if not {
p.check(.not)
}
name := p.check_name()
p.fspace()
if name in SupportedPlatforms {
ifdef_name := os_name_to_ifdef(name)
if not {
p.genln('#ifndef $ifdef_name')
}
else {
p.genln('#ifdef $ifdef_name')
}
p.check(.lcbr)
p.statements_no_rcbr()
if ! (p.tok == .dollar && p.peek() == .key_else) {
p.genln('#endif')
}
}
else {
println('Supported platforms:')
println(SupportedPlatforms)
p.error('unknown platform `$name`')
}
}
else if p.tok == .key_for {
p.next()
name := p.check_name()
if name != 'field' {
p.error('for field only')
}
p.check(.key_in)
p.check_name()
p.check(.dot)
p.check_name()// fields
p.check(.lcbr)
// for p.tok != .rcbr && p.tok != .eof {
res_name := p.check_name()
println(res_name)
p.check(.dot)
p.check(.dollar)
p.check(.name)
p.check(.assign)
p.cgen.start_tmp()
p.bool_expression()
val := p.cgen.end_tmp()
println(val)
p.check(.rcbr)
// }
}
else if p.tok == .key_else {
p.next()
p.check(.lcbr)
p.genln('#else')
p.statements_no_rcbr()
p.genln('#endif')
}
// $vweb.html()
// Compile vweb html template to V code, parse that V code and embed the resulting V functions
// that returns an html string
else if p.tok == .name && p.lit == 'vweb' {
path := p.cur_fn.name + '.html'
if !os.file_exists(path) {
p.error('vweb HTML template "$path" not found')
}
p.check(.name) // skip `vweb.html()` TODO
p.check(.dot)
p.check(.name)
p.check(.lpar)
p.check(.rpar)
v_code := tmpl.compile_template(path)
os.write_file('.vwebtmpl.v', v_code.clone()) // TODO don't need clone, compiler bug
p.genln('')
// Parse the function and embed resulting C code in current function so that
// all variables are available.
pos := p.cgen.lines.len - 1
mut pp := p.v.new_parser('.vwebtmpl.v', Pass.main)
os.rm('.vwebtmpl.v')
pp.is_vweb = true
pp.cur_fn = p.cur_fn // give access too all variables in current function
pp.parse()
tmpl_fn_body := p.cgen.lines.slice(pos + 2, p.cgen.lines.len).join('\n').clone()
end_pos := tmpl_fn_body.last_index('Builder_str( sb )') + 19 // TODO
p.cgen.lines = p.cgen.lines.left(pos)
p.genln('/////////////////// tmpl start')
p.genln(tmpl_fn_body.left(end_pos))
p.genln('/////////////////// tmpl end')
// `app.vweb.html(index_view())`
receiver := p.cur_fn.args[0]
dot := if receiver.is_mut { '->' } else { '.' }
p.genln('vweb__Context_html($receiver.name $dot vweb, tmpl_res)')
}
else {
p.error('bad comptime expr')
}
}
// #include, #flag, #v
fn (p mut Parser) chash() {
hash := p.lit.trim_space()
// println('chsh() file=$p.file is_sig=${p.is_sig()} hash="$hash"')
p.next()
is_sig := p.is_sig()
if hash.starts_with('flag ') {
mut flag := hash.right(5)
// No the right os? Skip!
// mut ok := true
if hash.contains('linux') && p.os != .linux {
return
}
else if hash.contains('darwin') && p.os != .mac {
return
}
else if hash.contains('windows') && (p.os != .windows && p.os != .msvc) {
return
}
// Remove "linux" etc from flag
if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') {
pos := flag.index(' ')
flag = flag.right(pos)
}
has_vroot := flag.contains('@VROOT')
flag = flag.trim_space().replace('@VROOT', p.vroot)
if p.table.flags.contains(flag) {
return
}
p.log('adding flag "$flag"')
// `@VROOT/thirdparty/glad/glad.o`, make sure it exists, otherwise build it
if has_vroot && flag.contains('.o') {
if p.os == .msvc {
build_thirdparty_obj_file_with_msvc(flag)
}
else {
build_thirdparty_obj_file(flag)
}
}
p.table.flags << flag
return
}
if hash.starts_with('include') {
if p.first_pass() && !is_sig {
p.cgen.includes << '#$hash'
return
}
}
// TODO remove after ui_mac.m is removed
else if hash.contains('embed') {
pos := hash.index('embed') + 5
file := hash.right(pos)
if p.pref.build_mode != BuildMode.default_mode {
p.genln('#include $file')
}
}
else if hash.contains('define') {
// Move defines on top
p.cgen.includes << '#$hash'
}
else if hash == 'v' {
println('v script')
//p.v_script = true
}
else {
if !p.can_chash {
p.error('bad token `#` (embedding C code is no longer supported)')
}
p.genln(hash)
}
}
// `user.$method()` (`method` is a string)
fn (p mut Parser) comptime_method_call(typ Type) {
p.cgen.cur_line = ''
p.check(.dollar)
var := p.check_name()
for method in typ.methods {
if method.typ != 'void' {
continue
}
receiver := method.args[0]
amp := if receiver.is_mut { '&' } else { '' }
p.gen('if ( string_eq($var, _STR("$method.name")) ) ${typ.name}_$method.name($amp $p.expr_var.name);')
}
p.check(.lpar)
p.check(.rpar)
}

View File

@ -30,6 +30,7 @@ mut:
returns_error bool returns_error bool
is_decl bool // type myfn fn(int, int) is_decl bool // type myfn fn(int, int)
defer_text string defer_text string
//gen_types []string
} }
fn (f &Fn) find_var(name string) Var { fn (f &Fn) find_var(name string) Var {
@ -110,7 +111,7 @@ fn (p mut Parser) fn_decl() {
defer { p.fgenln('\n') } defer { p.fgenln('\n') }
is_pub := p.tok == .key_pub is_pub := p.tok == .key_pub
is_live := p.attr == 'live' && !p.pref.is_so && p.pref.is_live is_live := p.attr == 'live' && !p.pref.is_so && p.pref.is_live
if p.attr == 'live' && p.first_run() && !p.pref.is_live && !p.pref.is_so { if p.attr == 'live' && p.first_pass() && !p.pref.is_live && !p.pref.is_so {
println('INFO: run `v -live program.v` if you want to use [live] functions') println('INFO: run `v -live program.v` if you want to use [live] functions')
} }
if is_pub { if is_pub {
@ -136,7 +137,7 @@ fn (p mut Parser) fn_decl() {
p.error('invalid receiver type `$receiver_typ` (`$receiver_typ` is an interface)') p.error('invalid receiver type `$receiver_typ` (`$receiver_typ` is an interface)')
} }
// Don't allow modifying types from a different module // Don't allow modifying types from a different module
if !p.first_run() && !p.builtin_pkg && T.mod != p.mod { if !p.first_pass() && !p.builtin_pkg && T.mod != p.mod {
println('T.mod=$T.mod') println('T.mod=$T.mod')
println('p.mod=$p.mod') println('p.mod=$p.mod')
p.error('cannot define new methods on non-local type `$receiver_typ`') p.error('cannot define new methods on non-local type `$receiver_typ`')
@ -203,7 +204,7 @@ fn (p mut Parser) fn_decl() {
if !is_c && !p.builtin_pkg && p.mod != 'main' && receiver_typ.len == 0 { if !is_c && !p.builtin_pkg && p.mod != 'main' && receiver_typ.len == 0 {
f.name = p.prepend_pkg(f.name) f.name = p.prepend_pkg(f.name)
} }
if p.first_run() && p.table.known_fn(f.name) && receiver_typ.len == 0 { if p.first_pass() && p.table.known_fn(f.name) && receiver_typ.len == 0 {
existing_fn := p.table.find_fn(f.name) existing_fn := p.table.find_fn(f.name)
// This existing function could be defined as C decl before (no body), then we don't need to throw an erro // This existing function could be defined as C decl before (no body), then we don't need to throw an erro
if !existing_fn.is_decl { if !existing_fn.is_decl {
@ -213,13 +214,19 @@ fn (p mut Parser) fn_decl() {
// Generic? // Generic?
mut is_generic := false mut is_generic := false
if p.tok == .lt { if p.tok == .lt {
is_generic = true
p.next() p.next()
gen_type := p.check_name() gen_type := p.check_name()
if gen_type != 'T' { if gen_type != 'T' {
p.error('only `T` is allowed as a generic type for now') p.error('only `T` is allowed as a generic type for now')
} }
p.check(.gt) p.check(.gt)
is_generic = true if p.first_pass() {
p.table.register_generic_fn(f.name)
} else {
//gen_types := p.table.fn_gen_types(f.name)
//println(gen_types)
}
} }
// Args (...) // Args (...)
p.fn_args(mut f) p.fn_args(mut f)
@ -247,14 +254,13 @@ fn (p mut Parser) fn_decl() {
p.fgen(' ') p.fgen(' ')
p.check(.lcbr) p.check(.lcbr)
} }
// Register option ? type // Register ?option type
if typ.starts_with('Option_') { if typ.starts_with('Option_') {
p.cgen.typedefs << 'typedef Option $typ;' p.cgen.typedefs << 'typedef Option $typ;'
} }
// Register function // Register function
f.typ = typ f.typ = typ
mut str_args := f.str_args(p.table) mut str_args := f.str_args(p.table)
// println('FN .decL $f.name typ=$f.typ str_args="$str_args"')
// Special case for main() args // Special case for main() args
if f.name == 'main' && !has_receiver { if f.name == 'main' && !has_receiver {
if str_args != '' || typ != 'void' { if str_args != '' || typ != 'void' {
@ -263,21 +269,19 @@ fn (p mut Parser) fn_decl() {
typ = 'int' typ = 'int'
str_args = 'int argc, char** argv' str_args = 'int argc, char** argv'
} }
dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so {
mut dll_export_linkage := '' '__declspec(dllexport) '
} else {
if p.os == .msvc && p.attr == 'live' && p.pref.is_so { ''
dll_export_linkage = '__declspec(dllexport) ' }
} // if p.file_name != '.vwebtmpl.v' {
// oif !p.name.ends_with('_vwebview') {
// Only in C code generate User_register() instead of register() if !p.is_vweb {
//println('SETTING CUR FN TO "$f.name" "$p.file_name"')
p.cur_fn = f
}
// Generate `User_register()` instead of `register()`
// Internally it's still stored as "register" in type User // Internally it's still stored as "register" in type User
// mut fn_name_cgen := f.name
// if receiver_typ != '' {
// fn_name_cgen = '${receiver_typ}_$f.name'
// fn_name_cgen = fn_name_cgen.replace(' ', '')
// fn_name_cgen = fn_name_cgen.replace('*', '')
// }
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
skip_main_in_test := f.name == 'main' && p.pref.is_test skip_main_in_test := f.name == 'main' && p.pref.is_test
@ -285,7 +289,27 @@ fn (p mut Parser) fn_decl() {
if p.pref.obfuscate { if p.pref.obfuscate {
p.genln('; // $f.name') p.genln('; // $f.name')
} }
p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') // Generate this function's body for all generic types
if is_generic {
gen_types := p.table.fn_gen_types(f.name)
// Remember current scanner position, go back here for each type
// TODO remove this once tokens are cached in `new_parser()`
cur_pos := p.scanner.pos
cur_tok := p.tok
cur_lit := p.lit
for gen_type in gen_types {
p.genln('$dll_export_linkage$typ ${fn_name_cgen}_$gen_type($str_args) {')
p.genln('// T start $p.pass ${p.strtok()}')
p.cur_gen_type = gen_type // TODO support more than T
p.statements()
p.scanner.pos = cur_pos
p.tok = cur_tok
p.lit = cur_lit
}
}
else {
p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {')
}
} }
if is_fn_header { if is_fn_header {
p.genln('$typ $fn_name_cgen($str_args);') p.genln('$typ $fn_name_cgen($str_args);')
@ -294,13 +318,12 @@ fn (p mut Parser) fn_decl() {
if is_c { if is_c {
p.fgenln('\n') p.fgenln('\n')
} }
p.cur_fn = f
// Register the method // Register the method
if receiver_typ != '' { if receiver_typ != '' {
mut receiver_t := p.table.find_type(receiver_typ) mut receiver_t := p.table.find_type(receiver_typ)
// No such type yet? It could be defined later. Create a new type. // No such type yet? It could be defined later. Create a new type.
// struct declaration later will modify it instead of creating a new one. // struct declaration later will modify it instead of creating a new one.
if p.first_run() && receiver_t.name == '' { if p.first_pass() && receiver_t.name == '' {
// println('fn decl !!!!!!! REG PH $receiver_typ') // println('fn decl !!!!!!! REG PH $receiver_typ')
p.table.register_type2(Type { p.table.register_type2(Type {
name: receiver_typ.replace('*', '') name: receiver_typ.replace('*', '')
@ -315,8 +338,9 @@ fn (p mut Parser) fn_decl() {
// println('register_fn typ=$typ isg=$is_generic') // println('register_fn typ=$typ isg=$is_generic')
p.table.register_fn(f) p.table.register_fn(f)
} }
if is_sig || p.first_run() || is_live || is_fn_header || skip_main_in_test { if is_sig || p.first_pass() || is_live || is_fn_header || skip_main_in_test {
// First pass? Skip the body for now [BIG] // First pass? Skip the body for now
// Look for generic calls.
if !is_sig && !is_fn_header { if !is_sig && !is_fn_header {
mut opened_scopes := 0 mut opened_scopes := 0
mut closed_scopes := 0 mut closed_scopes := 0
@ -328,10 +352,24 @@ fn (p mut Parser) fn_decl() {
closed_scopes++ closed_scopes++
} }
p.next() p.next()
// find `foo<Bar>()` in function bodies and register generic types
// TODO remove this once tokens are cached
if p.tok == .gt && p.prev_tok == .name && p.prev_tok2 == .lt &&
p.scanner.text[p.scanner.pos-1] != `T` {
p.scanner.pos -= 3
for p.scanner.pos > 0 && is_name_char(p.scanner.text[p.scanner.pos]) || p.scanner.text[p.scanner.pos] == `.` ||
p.scanner.text[p.scanner.pos] == `<` {
p.scanner.pos--
}
p.scanner.pos--
p.next()
// Run the function in the firt pass to register the generic type
p.name_expr()
}
if p.tok.is_decl() && !(p.prev_tok == .dot && p.tok == .key_type) { if p.tok.is_decl() && !(p.prev_tok == .dot && p.tok == .key_type) {
break break
} }
//fn body ended, and a new fn attribute declaration like [live] is starting? // fn body ended, and a new fn attribute declaration like [live] is starting?
if closed_scopes > opened_scopes && p.prev_tok == .rcbr { if closed_scopes > opened_scopes && p.prev_tok == .rcbr {
if p.tok == .lsbr { if p.tok == .lsbr {
break break
@ -340,18 +378,18 @@ 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() && p.mod == 'main' { if is_live && p.first_pass() && p.mod == 'main' {
//println('ADDING SO FN $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 )'
} }
// Actual fn declaration! // Function definition that goes to the top of the C file.
mut fn_decl := '$dll_export_linkage$typ $fn_name_cgen($str_args)' mut fn_decl := '$dll_export_linkage$typ $fn_name_cgen($str_args)'
if p.pref.obfuscate { if p.pref.obfuscate {
fn_decl += '; // ${f.name}' fn_decl += '; // $f.name'
} }
// Add function definition to the top // Add function definition to the top
if !is_c && f.name != 'main' && p.first_run() { if !is_c && f.name != 'main' && p.first_pass() {
// TODO hack to make Volt compile without -embed_vlib // TODO hack to make Volt compile without -embed_vlib
if f.name == 'darwin__nsstring' && p.pref.build_mode == .default_mode { if f.name == 'darwin__nsstring' && p.pref.build_mode == .default_mode {
return return
@ -360,12 +398,10 @@ fn (p mut Parser) fn_decl() {
} }
return return
} }
if p.attr == 'live' && p.pref.is_so { if p.attr == 'live' && p.pref.is_so {
//p.genln('// live_function body start') //p.genln('// live_function body start')
p.genln('pthread_mutex_lock(&live_fn_mutex);') p.genln('pthread_mutex_lock(&live_fn_mutex);')
} }
if f.name == 'main' || f.name == 'WinMain' { if f.name == 'main' || f.name == 'WinMain' {
p.genln('init_consts();') p.genln('init_consts();')
if p.table.imports.contains('os') { if p.table.imports.contains('os') {
@ -404,13 +440,18 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);
// println('IS SIG .key_returnING tok=${p.strtok()}') // println('IS SIG .key_returnING tok=${p.strtok()}')
return return
} }
// We are in profile mode? Start counting at the beginning of the function (save current time). // Profiling mode? Start counting at the beginning of the function (save current time).
if p.pref.is_prof && f.name != 'main' && f.name != 'time__ticks' { if p.pref.is_prof && f.name != 'main' && f.name != 'time__ticks' {
p.genln('double _PROF_START = time__ticks();//$f.name') p.genln('double _PROF_START = time__ticks();//$f.name')
cgen_name := p.table.cgen_name(f) cgen_name := p.table.cgen_name(f)
f.defer_text = ' ${cgen_name}_time += time__ticks() - _PROF_START;' f.defer_text = ' ${cgen_name}_time += time__ticks() - _PROF_START;'
} }
if is_generic {
// Don't need to generate body for the actual generic definition
p.cgen.nogen = true
}
p.statements_no_rcbr() p.statements_no_rcbr()
p.cgen.nogen = false
// Print counting result after all statements in main // Print counting result after all statements in main
if p.pref.is_prof && f.name == 'main' { if p.pref.is_prof && f.name == 'main' {
p.genln(p.print_prof_counters()) p.genln(p.print_prof_counters())
@ -420,12 +461,10 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);
if typ != 'void' && !p.returns && f.name != 'main' && f.name != 'WinMain' { if typ != 'void' && !p.returns && f.name != 'main' && f.name != 'WinMain' {
p.error('$f.name must return "$typ"') p.error('$f.name must return "$typ"')
} }
if p.attr == 'live' && p.pref.is_so { if p.attr == 'live' && p.pref.is_so {
//p.genln('// live_function body end') //p.genln('// live_function body end')
p.genln('pthread_mutex_unlock(&live_fn_mutex);') p.genln('pthread_mutex_unlock(&live_fn_mutex);')
} }
// {} closed correctly? scope_level should be 0 // {} closed correctly? scope_level should be 0
if p.mod == 'main' { if p.mod == 'main' {
// println(p.cur_fn.scope_level) // println(p.cur_fn.scope_level)
@ -436,12 +475,16 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);
// Make sure all vars in this function are used (only in main for now) // Make sure all vars in this function are used (only in main for now)
// if p.builtin_pkg || p.mod == 'os' ||p.mod=='http'{ // if p.builtin_pkg || p.mod == 'os' ||p.mod=='http'{
if p.mod != 'main' { if p.mod != 'main' {
p.genln('}') if !is_generic {
p.genln('}')
}
return return
} }
p.check_unused_variables() p.check_unused_variables()
p.cur_fn = EmptyFn p.cur_fn = EmptyFn
p.genln('}') if !is_generic {
p.genln('}')
}
} }
fn (p mut Parser) check_unused_variables() { fn (p mut Parser) check_unused_variables() {
@ -539,6 +582,9 @@ fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type
} }
fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) {
if p.fileis('vtalk2.v') {
//println('FN CALL k $f.name')
}
if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.pkg != p.mod { if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.pkg != p.mod {
p.error('function `$f.name` is private') p.error('function `$f.name` is private')
} }
@ -550,7 +596,25 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin
p.error('use `malloc()` instead of `C.malloc()`') p.error('use `malloc()` instead of `C.malloc()`')
} }
} }
cgen_name := p.table.cgen_name(f) mut cgen_name := p.table.cgen_name(f)
p.next()
mut gen_type := ''
if p.tok == .lt {
p.check(.lt)
gen_type = p.check_name()
// `foo<Bar>()`
// If we are in the first pass, we need to add `Bar` type to the generic function `foo`,
// so that generic `foo`s body can be generated for each type in the second pass.
if p.first_pass() {
println('reging $gen_type in $f.name')
p.table.register_generic_fn_type(f.name, gen_type)
// Function bodies are skipped in the first passed, we only need to register the generic type here.
return
}
cgen_name += '_' + gen_type
p.check(.gt)
}
// if p.pref.is_prof { // if p.pref.is_prof {
// p.cur_fn.called_fns << cgen_name // p.cur_fn.called_fns << cgen_name
// } // }
@ -590,8 +654,8 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin
} }
p.cgen.set_placeholder(method_ph, '$cast $method_call') p.cgen.set_placeholder(method_ph, '$cast $method_call')
} }
p.next() // foo<Bar>()
p.fn_call_args(f) p.fn_call_args(mut f)
p.gen(')') p.gen(')')
p.calling_c = false p.calling_c = false
// println('end of fn call typ=$f.typ') // println('end of fn call typ=$f.typ')
@ -648,7 +712,7 @@ fn (p mut Parser) fn_args(f mut Fn) {
'\nreturn values instead: `foo(n mut int)` => `foo(n int) int`') '\nreturn values instead: `foo(n mut int)` => `foo(n int) int`')
} }
for name in names { for name in names {
if !p.first_run() && !p.table.known_type(typ) { if !p.first_pass() && !p.table.known_type(typ) {
p.error('fn_args: unknown type $typ') p.error('fn_args: unknown type $typ')
} }
if is_mut { if is_mut {
@ -678,7 +742,7 @@ fn (p mut Parser) fn_args(f mut Fn) {
} }
// foo *(1, 2, 3, mut bar)* // foo *(1, 2, 3, mut bar)*
fn (p mut Parser) fn_call_args(f *Fn) *Fn { fn (p mut Parser) fn_call_args(f mut Fn) *Fn {
// p.gen('(') // p.gen('(')
// println('fn_call_args() name=$f.name args.len=$f.args.len') // println('fn_call_args() name=$f.name args.len=$f.args.len')
// C func. # of args is not known // C func. # of args is not known
@ -762,10 +826,10 @@ fn (p mut Parser) fn_call_args(f *Fn) *Fn {
p.error(error_msg) p.error(error_msg)
} }
p.cgen.resetln(p.cgen.cur_line.left(index)) p.cgen.resetln(p.cgen.cur_line.left(index))
p.create_type_string(T, name) p.scanner.create_type_string(T, name)
p.cgen.cur_line.replace(typ, '') p.cgen.cur_line.replace(typ, '')
p.next() p.next()
return p.fn_call_args(f) return p.fn_call_args(mut f)
} }
p.error(error_msg) p.error(error_msg)
} }

View File

@ -22,7 +22,7 @@ fn (p mut Parser) gen_json_for_type(typ Type) {
if t == 'int' || t == 'string' || t == 'bool' { if t == 'int' || t == 'string' || t == 'bool' {
return return
} }
if p.first_run() { if p.first_pass() {
return return
} }
// println('gen_json_for_type( $typ.name )') // println('gen_json_for_type( $typ.name )')

View File

@ -179,7 +179,7 @@ fn (v mut V) compile() {
p.parse() p.parse()
} }
// Main pass // Main pass
cgen.run = Pass.main cgen.pass = Pass.main
if v.pref.is_play { if v.pref.is_play {
cgen.genln('#define VPLAY (1) ') cgen.genln('#define VPLAY (1) ')
} }

View File

@ -4,9 +4,11 @@
module main module main
import os import (
import rand os
import strings rand
strings
)
struct Var { struct Var {
mut: mut:
@ -35,6 +37,7 @@ struct Parser {
file_path string // "/home/user/hello.v" file_path string // "/home/user/hello.v"
file_name string // "hello.v" file_name string // "hello.v"
mut: mut:
v *V
scanner *Scanner scanner *Scanner
// tokens []Token // TODO cache all tokens, right now they have to be scanned twice // tokens []Token // TODO cache all tokens, right now they have to be scanned twice
token_idx int token_idx int
@ -45,7 +48,7 @@ mut:
cgen *CGen cgen *CGen
table *Table table *Table
import_table *FileImportTable // Holds imports for just the file being parsed import_table *FileImportTable // Holds imports for just the file being parsed
run Pass // TODO rename `run` to `pass` pass Pass
os OS os OS
mod string mod string
inside_const bool inside_const bool
@ -73,6 +76,8 @@ mut:
var_decl_name string // To allow declaring the variable so that it can be used in the struct initialization var_decl_name string // To allow declaring the variable so that it can be used in the struct initialization
building_v bool building_v bool
is_alloc bool // Whether current expression resulted in an allocation is_alloc bool // Whether current expression resulted in an allocation
cur_gen_type string // "App" to replace "T" in current generic function
is_vweb bool
} }
const ( const (
@ -84,29 +89,30 @@ const (
MaxModuleDepth = 4 MaxModuleDepth = 4
) )
fn (c mut V) new_parser(path string, run Pass) Parser { fn (v mut V) new_parser(path string, pass Pass) Parser {
c.log('new_parser("$path")') v.log('new_parser("$path")')
c.cgen.run = run v.cgen.pass = pass
mut p := Parser { mut p := Parser {
v: v
file_path: path file_path: path
file_name: path.all_after('/') file_name: path.all_after('/')
scanner: new_scanner(path) scanner: new_scanner(path)
table: c.table table: v.table
import_table: new_file_import_table(path) import_table: new_file_import_table(path)
cur_fn: EmptyFn cur_fn: EmptyFn
cgen: c.cgen cgen: v.cgen
is_script: (c.pref.is_script && path == c.dir) is_script: (v.pref.is_script && path == v.dir)
pref: c.pref pref: v.pref
os: c.os os: v.os
run: run pass: pass
vroot: c.vroot vroot: v.vroot
building_v: !c.pref.is_repl && (path.contains('compiler/') || building_v: !v.pref.is_repl && (path.contains('compiler/') ||
path.contains('v/vlib')) path.contains('v/vlib'))
} }
c.cgen.line_directives = c.pref.is_debuggable v.cgen.line_directives = v.pref.is_debuggable
c.cgen.file = path v.cgen.file = path
p.next() p.next()
// p.scanner.debug_tokens() // p.scanner.debug_tokens()
@ -132,7 +138,7 @@ fn (p &Parser) log(s string) {
} }
fn (p mut Parser) parse() { fn (p mut Parser) parse() {
p.log('\nparse() run=$p.run file=$p.file_name tok=${p.strtok()}')// , "script_file=", script_file) p.log('\nparse() run=$p.pass file=$p.file_name tok=${p.strtok()}')// , "script_file=", script_file)
// `module main` is not required if it's a single file program // `module main` is not required if it's a single file program
if p.is_script || p.pref.is_test { if p.is_script || p.pref.is_test {
p.mod = 'main' p.mod = 'main'
@ -158,7 +164,7 @@ fn (p mut Parser) parse() {
p.table.register_package(fq_mod) p.table.register_package(fq_mod)
// replace "." with "_dot_" in module name for C variable names // replace "." with "_dot_" in module name for C variable names
p.mod = fq_mod.replace('.', '_dot_') p.mod = fq_mod.replace('.', '_dot_')
if p.run == .imports { if p.pass == .imports {
for p.tok == .key_import && p.peek() != .key_const { for p.tok == .key_import && p.peek() != .key_const {
p.imports() p.imports()
} }
@ -256,7 +262,7 @@ fn (p mut Parser) parse() {
p.cur_fn = MainFn p.cur_fn = MainFn
p.check_unused_variables() p.check_unused_variables()
} }
if false && !p.first_run() && p.fileis('main.v') { if false && !p.first_pass() && p.fileis('main.v') {
out := os.create('/var/tmp/fmt.v') or { out := os.create('/var/tmp/fmt.v') or {
panic('failed to create fmt.v') panic('failed to create fmt.v')
return return
@ -270,7 +276,7 @@ fn (p mut Parser) parse() {
if p.is_script && !p.pref.is_test { if p.is_script && !p.pref.is_test {
// cur_fn is empty since there was no fn main declared // cur_fn is empty since there was no fn main declared
// we need to set it to save and find variables // we need to set it to save and find variables
if p.first_run() { if p.first_pass() {
if p.cur_fn.name == '' { if p.cur_fn.name == '' {
p.cur_fn = MainFn p.cur_fn = MainFn
} }
@ -320,43 +326,6 @@ fn (p mut Parser) imports() {
p.register_import() p.register_import()
} }
fn (p mut Parser) register_import() {
if p.tok != .name {
p.error('bad import format')
}
mut pkg := p.lit.trim_space()
mut mod_alias := pkg
// submodule support
mut depth := 1
p.next()
for p.tok == .dot {
p.check(.dot)
submodule := p.check_name()
mod_alias = submodule
pkg += '.' + submodule
depth++
if depth > MaxModuleDepth {
p.error('module depth of $MaxModuleDepth exceeded: $pkg')
}
}
// aliasing (import encoding.base64 as b64)
if p.tok == .key_as && p.peek() == .name {
p.check(.key_as)
mod_alias = p.check_name()
}
// add import to file scope import table
p.import_table.register_alias(mod_alias, pkg)
// Make sure there are no duplicate imports
if p.table.imports.contains(pkg) {
return
}
p.log('adding import $pkg')
p.table.imports << pkg
p.table.register_package(pkg)
p.fgenln(' ' + pkg)
}
fn (p mut Parser) const_decl() { fn (p mut Parser) const_decl() {
is_import := p.tok == .key_import is_import := p.tok == .key_import
p.inside_const = true p.inside_const = true
@ -371,9 +340,9 @@ fn (p mut Parser) const_decl() {
for p.tok == .name { for p.tok == .name {
// `Age = 20` // `Age = 20`
mut name := p.check_name() mut name := p.check_name()
if p.pref.is_play && ! (name[0] >= `A` && name[0] <= `Z`) { //if ! (name[0] >= `A` && name[0] <= `Z`) {
p.error('const name must be capitalized') //p.error('const name must be capitalized')
} //}
// Imported consts (like GL_TRIANG.leS) dont need pkg prepended (gl__GL_TRIANG.leS) // Imported consts (like GL_TRIANG.leS) dont need pkg prepended (gl__GL_TRIANG.leS)
if !is_import { if !is_import {
name = p.prepend_pkg(name) name = p.prepend_pkg(name)
@ -383,11 +352,11 @@ fn (p mut Parser) const_decl() {
p.check_space(.assign) p.check_space(.assign)
typ = p.expression() typ = p.expression()
} }
if p.first_run() && !is_import && p.table.known_const(name) { if p.first_pass() && !is_import && p.table.known_const(name) {
p.error('redefinition of `$name`') p.error('redefinition of `$name`')
} }
p.table.register_const(name, typ, p.mod, is_import) p.table.register_const(name, typ, p.mod, is_import)
if p.run == .main && !is_import { if p.pass == .main && !is_import {
// TODO hack // TODO hack
// cur_line has const's value right now. if it's just a number, then optimize generation: // cur_line has const's value right now. if it's just a number, then optimize generation:
// output a #define so that we don't pollute the binary with unnecessary global vars // output a #define so that we don't pollute the binary with unnecessary global vars
@ -437,7 +406,7 @@ fn (p mut Parser) interface_method(field_name, receiver string) &Fn {
is_method: true is_method: true
receiver_typ: receiver receiver_typ: receiver
} }
p.log('is interface. field=$field_name run=$p.run') p.log('is interface. field=$field_name run=$p.pass')
p.fn_args(mut method) p.fn_args(mut method)
if p.scanner.has_gone_over_line_end() { if p.scanner.has_gone_over_line_end() {
method.typ = 'void' method.typ = 'void'
@ -488,7 +457,7 @@ fn (p mut Parser) struct_decl() {
if !is_c && !p.builtin_pkg && p.mod != 'main' { if !is_c && !p.builtin_pkg && p.mod != 'main' {
name = p.prepend_pkg(name) name = p.prepend_pkg(name)
} }
if p.run == .decl && p.table.known_type(name) { if p.pass == .decl && p.table.known_type(name) {
p.error('`$name` redeclared') p.error('`$name` redeclared')
} }
// Generate type definitions // Generate type definitions
@ -543,7 +512,7 @@ fn (p mut Parser) struct_decl() {
fmt_max_len = field.name.len fmt_max_len = field.name.len
} }
} }
println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.run') println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass')
*/ */
mut did_gen_something := false mut did_gen_something := false
for p.tok != .rcbr { for p.tok != .rcbr {
@ -616,7 +585,7 @@ fn (p mut Parser) struct_decl() {
p.fgenln('') p.fgenln('')
} }
if !is_ph && p.first_run() { if !is_ph && p.first_pass() {
p.table.register_type2(typ) p.table.register_type2(typ)
//println('registering 1 nrfields=$typ.fields.len') //println('registering 1 nrfields=$typ.fields.len')
} }
@ -654,7 +623,7 @@ fn (p mut Parser) enum_decl(_enum_name string) {
fields << field fields << field
p.fgenln('') p.fgenln('')
name := '${p.mod}__${enum_name}_$field' name := '${p.mod}__${enum_name}_$field'
if p.run == .main { if p.pass == .main {
p.cgen.consts << '#define $name $val' p.cgen.consts << '#define $name $val'
} }
if p.tok == .comma { if p.tok == .comma {
@ -738,19 +707,14 @@ if p.scanner.line_comment != '' {
fn (p mut Parser) error(s string) { fn (p mut Parser) error(s string) {
// Dump all vars and types for debugging // Dump all vars and types for debugging
if false { if p.pref.is_debug {
//file_types := os.create('$TmpPath/types')
//file_vars := os.create('$TmpPath/vars')
// ////debug("ALL T", q.J(p.table.types))
// os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types)) // os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types))
// //debug("ALL V", q.J(p.table.vars))
// os.write_to_file('/var/tmp/lang.vars', q.J(p.table.vars)) // os.write_to_file('/var/tmp/lang.vars', q.J(p.table.vars))
//file_types.close() os.write_file('fns.txt', p.table.debug_fns())
//file_vars.close()
}
if p.pref.is_verbose {
println('pass=$p.run fn=`$p.cur_fn.name`')
} }
//if p.pref.is_verbose {
println('pass=$p.pass fn=`$p.cur_fn.name`')
//}
p.cgen.save() p.cgen.save()
// V git pull hint // V git pull hint
cur_path := os.getwd() cur_path := os.getwd()
@ -769,8 +733,8 @@ fn (p mut Parser) error(s string) {
p.scanner.error(s.replace('array_', '[]').replace('__', '.').replace('Option_', '?')) p.scanner.error(s.replace('array_', '[]').replace('__', '.').replace('Option_', '?'))
} }
fn (p &Parser) first_run() bool { fn (p &Parser) first_pass() bool {
return p.run == .decl return p.pass == .decl
} }
// TODO return Type instead of string? // TODO return Type instead of string?
@ -890,13 +854,13 @@ fn (p mut Parser) get_type() string {
mut t := p.table.find_type(typ) mut t := p.table.find_type(typ)
// "typ" not found? try "pkg__typ" // "typ" not found? try "pkg__typ"
if t.name == '' && !p.builtin_pkg { if t.name == '' && !p.builtin_pkg {
// && !p.first_run() { // && !p.first_pass() {
if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') && if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') &&
!typ.starts_with('[') { !typ.starts_with('[') {
typ = p.prepend_pkg(typ) typ = p.prepend_pkg(typ)
} }
t = p.table.find_type(typ) t = p.table.find_type(typ)
if t.name == '' && !p.pref.translated && !p.first_run() && !typ.starts_with('[') { if t.name == '' && !p.pref.translated && !p.first_pass() && !typ.starts_with('[') {
println('get_type() bad type') println('get_type() bad type')
// println('all registered types:') // println('all registered types:')
// for q in p.table.types { // for q in p.table.types {
@ -919,7 +883,7 @@ fn (p mut Parser) get_type() string {
} }
else if is_arr { else if is_arr {
typ = 'array_$typ' typ = 'array_$typ'
// p.log('ARR TYPE="$typ" run=$p.run') // p.log('ARR TYPE="$typ" run=$p.pass')
// We come across "[]User" etc ? // We come across "[]User" etc ?
p.register_array(typ) p.register_array(typ)
} }
@ -1050,14 +1014,6 @@ fn (p mut Parser) vh_genln(s string) {
p.vh_lines << s p.vh_lines << s
} }
fn (p mut Parser) fmt_inc() {
p.scanner.fmt_indent++
}
fn (p mut Parser) fmt_dec() {
p.scanner.fmt_indent--
}
fn (p mut Parser) statement(add_semi bool) string { fn (p mut Parser) statement(add_semi bool) string {
p.cgen.is_tmp = false p.cgen.is_tmp = false
tok := p.tok tok := p.tok
@ -1342,13 +1298,17 @@ fn (p mut Parser) bterm() string {
// also called on *, &, @, . (enum) // also called on *, &, @, . (enum)
fn (p mut Parser) name_expr() string { fn (p mut Parser) name_expr() string {
p.log('\nname expr() pass=$p.run tok=${p.tok.str()} $p.lit') if p.fileis('vtalk') {
//println('\nname expr() pass=$p.pass tok=${p.tok.str()} lit="$p.lit" ${p.scanner.line_nr+1}')
}
// print('known type:') // print('known type:')
// println(p.table.known_type(p.lit)) // println(p.table.known_type(p.lit))
// hack for struct_init TODO // hack for struct_init TODO
/*
hack_pos := p.scanner.pos hack_pos := p.scanner.pos
hack_tok := p.tok hack_tok := p.tok
hack_lit := p.lit hack_lit := p.lit
*/
ph := p.cgen.add_placeholder() ph := p.cgen.add_placeholder()
// amp // amp
ptr := p.tok == .amp ptr := p.tok == .amp
@ -1379,7 +1339,7 @@ fn (p mut Parser) name_expr() string {
} }
// enum value? (`color == .green`) // enum value? (`color == .green`)
if p.tok == .dot { if p.tok == .dot {
//println('got enum dot val $p.left_type pass=$p.run $p.scanner.line_nr left=$p.left_type') //println('got enum dot val $p.left_type pass=$p.pass $p.scanner.line_nr left=$p.left_type')
T := p.find_type(p.expected_type) T := p.find_type(p.expected_type)
if T.is_enum { if T.is_enum {
p.check(.dot) p.check(.dot)
@ -1445,7 +1405,7 @@ fn (p mut Parser) name_expr() string {
} }
return typ return typ
} }
// if known_type || is_c_struct_init || (p.first_run() && p.peek() == .lcbr) { // if known_type || is_c_struct_init || (p.first_pass() && p.peek() == .lcbr) {
// known type? int(4.5) or Color.green (enum) // known type? int(4.5) or Color.green (enum)
if p.table.known_type(name) { if p.table.known_type(name) {
// float(5), byte(0), (*int)(ptr) etc // float(5), byte(0), (*int)(ptr) etc
@ -1480,16 +1440,24 @@ fn (p mut Parser) name_expr() string {
} }
else if p.peek() == .lcbr { else if p.peek() == .lcbr {
// go back to name start (pkg.name) // go back to name start (pkg.name)
/*
p.scanner.pos = hack_pos p.scanner.pos = hack_pos
p.tok = hack_tok p.tok = hack_tok
p.lit = hack_lit p.lit = hack_lit
*/
// TODO hack. If it's a C type, we may need to add struct before declaration: // TODO hack. If it's a C type, we may need to add struct before declaration:
// a := &C.A{} ==> struct A* a = malloc(sizeof(struct A)); // a := &C.A{} ==> struct A* a = malloc(sizeof(struct A));
if is_c_struct_init { if is_c_struct_init {
p.is_c_struct_init = true p.is_c_struct_init = true
p.cgen.insert_before('struct /*c struct init*/') p.cgen.insert_before('struct /*c struct init*/')
} }
return p.struct_init(is_c_struct_init) if ptr {
name += '*' // `&User{}` => type `User*`
}
if name == 'T' {
name = p.cur_gen_type
}
return p.struct_init(name, is_c_struct_init)
} }
} }
// C fn // C fn
@ -1529,7 +1497,7 @@ fn (p mut Parser) name_expr() string {
mut f := p.table.find_fn(name) mut f := p.table.find_fn(name)
if f.name == '' { if f.name == '' {
// We are in a second pass, that means this function was not defined, throw an error. // We are in a second pass, that means this function was not defined, throw an error.
if !p.first_run() { if !p.first_pass() {
// V script? Try os module. // V script? Try os module.
if p.v_script { if p.v_script {
name = name.replace('main__', 'os__') name = name.replace('main__', 'os__')
@ -1554,7 +1522,8 @@ fn (p mut Parser) name_expr() string {
} }
// no () after func, so func is an argument, just gen its name // no () after func, so func is an argument, just gen its name
// TODO verify this and handle errors // TODO verify this and handle errors
if p.peek() != .lpar { peek := p.peek()
if peek != .lpar && peek != .lt {
p.gen(p.table.cgen_name(f)) p.gen(p.table.cgen_name(f))
p.next() p.next()
return 'void*' return 'void*'
@ -1564,6 +1533,9 @@ fn (p mut Parser) name_expr() string {
// p.error('`$f.name` used as value') // p.error('`$f.name` used as value')
} }
p.log('calling function') p.log('calling function')
if p.fileis('vtalk') {
//println('calling fn $f.name')
}
p.fn_call(f, 0, '', '') p.fn_call(f, 0, '', '')
// dot after a function call: `get_user().age` // dot after a function call: `get_user().age`
if p.tok == .dot { if p.tok == .dot {
@ -1593,7 +1565,7 @@ fn (p mut Parser) var_expr(v Var) string {
//p.print_tok() //p.print_tok()
T := p.table.find_type(typ) T := p.table.find_type(typ)
p.gen('(') p.gen('(')
p.fn_call_args(T.func) p.fn_call_args(mut T.func)
p.gen(')') p.gen(')')
typ = T.func.typ typ = T.func.typ
} }
@ -1601,7 +1573,6 @@ fn (p mut Parser) var_expr(v Var) string {
// users[0].name // users[0].name
if p.tok == .lsbr { if p.tok == .lsbr {
typ = p.index_expr(typ, fn_ph) typ = p.index_expr(typ, fn_ph)
// ////println('QQQQ KEK $typ')
} }
// a.b.c().d chain // a.b.c().d chain
// mut dc := 0 // mut dc := 0
@ -1651,6 +1622,7 @@ fn (p mut Parser) var_expr(v Var) string {
return typ return typ
} }
// for debugging only
fn (p &Parser) fileis(s string) bool { fn (p &Parser) fileis(s string) bool {
return p.scanner.file_path.contains(s) return p.scanner.file_path.contains(s)
} }
@ -1659,19 +1631,23 @@ fn (p &Parser) fileis(s string) bool {
// user.company.name => `str_typ` is `Company` // user.company.name => `str_typ` is `Company`
fn (p mut Parser) dot(str_typ string, method_ph int) string { fn (p mut Parser) dot(str_typ string, method_ph int) string {
p.check(.dot) p.check(.dot)
typ := p.find_type(str_typ)
if typ.name.len == 0 {
p.error('dot(): cannot find type `$str_typ`')
}
if p.tok == .dollar {
p.comptime_method_call(typ)
return 'void'
}
field_name := p.lit field_name := p.lit
p.fgen(field_name) p.fgen(field_name)
p.log('dot() field_name=$field_name typ=$str_typ') p.log('dot() field_name=$field_name typ=$str_typ')
//if p.fileis('main.v') { //if p.fileis('main.v') {
//println('dot() field_name=$field_name typ=$str_typ prev_tok=${prev_tok.str()}') //println('dot() field_name=$field_name typ=$str_typ prev_tok=${prev_tok.str()}')
//} //}
typ := p.find_type(str_typ)
if typ.name.len == 0 {
p.error('dot(): cannot find type `$str_typ`')
}
has_field := p.table.type_has_field(typ, field_name) has_field := p.table.type_has_field(typ, field_name)
has_method := p.table.type_has_method(typ, field_name) has_method := p.table.type_has_method(typ, field_name)
if !typ.is_c && !has_field && !has_method && !p.first_run() { if !typ.is_c && !has_field && !has_method && !p.first_pass() {
if typ.name.starts_with('Option_') { if typ.name.starts_with('Option_') {
opt_type := typ.name.right(7) opt_type := typ.name.right(7)
p.error('unhandled option type: $opt_type?') p.error('unhandled option type: $opt_type?')
@ -1944,7 +1920,7 @@ fn (p mut Parser) index_expr(typ string, fn_ph int) string {
// returns resulting type // returns resulting type
fn (p mut Parser) expression() string { fn (p mut Parser) expression() string {
if p.scanner.file_path.contains('test_test') { if p.scanner.file_path.contains('test_test') {
println('expression() pass=$p.run tok=') println('expression() pass=$p.pass tok=')
p.print_tok() p.print_tok()
} }
p.cgen('/* expr start*/') p.cgen('/* expr start*/')
@ -2298,32 +2274,6 @@ fn (p mut Parser) char_expr() {
} }
fn (p mut Parser) typ_to_fmt(typ string, level int) string {
t := p.table.find_type(typ)
if t.is_enum {
return '%d'
}
switch typ {
case 'string': return '%.*s'
case 'ustring': return '%.*s'
case 'byte', 'int', 'char', 'byte', 'bool', 'u32', 'i32', 'i16', 'u16', 'i8', 'u8': return '%d'
case 'f64', 'f32': return '%f'
case 'i64', 'u64': return '%lld'
case 'byte*', 'byteptr': return '%s'
// case 'array_string': return '%s'
// case 'array_int': return '%s'
case 'void': p.error('cannot interpolate this value')
default:
if typ.ends_with('*') {
return '%p'
}
}
if t.parent != '' && level == 0 {
return p.typ_to_fmt(t.parent, level+1)
}
return ''
}
fn format_str(str string) string { fn format_str(str string) string {
str = str.replace('"', '\\"') str = str.replace('"', '\\"')
$if windows { $if windows {
@ -2546,7 +2496,7 @@ fn (p mut Parser) array_init() string {
if is_fixed_size { if is_fixed_size {
p.next() p.next()
p.gen(' }') p.gen(' }')
if !p.first_run() { if !p.first_pass() {
// If we are defining a const array, we don't need to specify the type: // If we are defining a const array, we don't need to specify the type:
// `a = {1,2,3}`, not `a = (int[]) {1,2,3}` // `a = {1,2,3}`, not `a = (int[]) {1,2,3}`
if p.inside_const { if p.inside_const {
@ -2567,8 +2517,8 @@ fn (p mut Parser) array_init() string {
} }
p.gen(' })') p.gen(' })')
// p.gen('$new_arr($vals.len, $vals.len, sizeof($typ), ($typ[]) $c_arr );') // p.gen('$new_arr($vals.len, $vals.len, sizeof($typ), ($typ[]) $c_arr );')
// TODO why need !first_run()?? Otherwise it goes to the very top of the out.c file // TODO why need !first_pass()?? Otherwise it goes to the very top of the out.c file
if !p.first_run() { if !p.first_pass() {
if i == 0 { if i == 0 {
p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) {EMPTY_STRUCT_INIT ') p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) {EMPTY_STRUCT_INIT ')
} else { } else {
@ -2580,32 +2530,9 @@ fn (p mut Parser) array_init() string {
return typ return typ
} }
fn (p mut Parser) register_array(typ string) { fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string {
if typ.contains('*') {
println('bad arr $typ')
return
}
if !p.table.known_type(typ) {
p.register_type_with_parent(typ, 'array')
p.cgen.typedefs << 'typedef array $typ;'
}
}
fn (p mut Parser) register_map(typ string) {
if typ.contains('*') {
println('bad map $typ')
return
}
if !p.table.known_type(typ) {
p.register_type_with_parent(typ, 'map')
p.cgen.typedefs << 'typedef map $typ;'
}
}
fn (p mut Parser) struct_init(is_c_struct_init bool) string {
p.is_struct_init = true p.is_struct_init = true
mut typ := p.get_type() p.next()
p.scanner.fmt_out.cut(typ.len) p.scanner.fmt_out.cut(typ.len)
ptr := typ.contains('*') ptr := typ.contains('*')
// TODO tm struct struct bug // TODO tm struct struct bug
@ -2810,153 +2737,6 @@ fn os_name_to_ifdef(name string) string {
return '' return ''
} }
fn (p mut Parser) comp_time() {
p.check(.dollar)
if p.tok == .key_if {
p.check(.key_if)
p.fspace()
not := p.tok == .not
if not {
p.check(.not)
}
name := p.check_name()
p.fspace()
if name in SupportedPlatforms {
ifdef_name := os_name_to_ifdef(name)
if not {
p.genln('#ifndef $ifdef_name')
}
else {
p.genln('#ifdef $ifdef_name')
}
p.check(.lcbr)
p.statements_no_rcbr()
if ! (p.tok == .dollar && p.peek() == .key_else) {
p.genln('#endif')
}
}
else {
println('Supported platforms:')
println(SupportedPlatforms)
p.error('unknown platform `$name`')
}
}
else if p.tok == .key_for {
p.next()
name := p.check_name()
if name != 'field' {
p.error('for field only')
}
p.check(.key_in)
p.check_name()
p.check(.dot)
p.check_name()// fields
p.check(.lcbr)
// for p.tok != .rcbr && p.tok != .eof {
res_name := p.check_name()
println(res_name)
p.check(.dot)
p.check(.dollar)
p.check(.name)
p.check(.assign)
p.cgen.start_tmp()
p.bool_expression()
val := p.cgen.end_tmp()
println(val)
p.check(.rcbr)
// }
}
else if p.tok == .key_else {
p.next()
p.check(.lcbr)
p.genln('#else')
p.statements_no_rcbr()
p.genln('#endif')
}
else {
p.error('bad comptime expr')
}
}
fn (p mut Parser) chash() {
hash := p.lit.trim_space()
// println('chsh() file=$p.file is_sig=${p.is_sig()} hash="$hash"')
p.next()
is_sig := p.is_sig()
if is_sig {
// p.cgen.nogen = true
}
if hash.starts_with('flag ') {
mut flag := hash.right(5)
// No the right os? Skip!
// mut ok := true
if hash.contains('linux') && p.os != .linux {
return
}
else if hash.contains('darwin') && p.os != .mac {
return
}
else if hash.contains('windows') && (p.os != .windows && p.os != .msvc) {
return
}
// Remove "linux" etc from flag
if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') {
pos := flag.index(' ')
flag = flag.right(pos)
}
has_vroot := flag.contains('@VROOT')
flag = flag.trim_space().replace('@VROOT', p.vroot)
if p.table.flags.contains(flag) {
return
}
p.log('adding flag "$flag"')
// `@VROOT/thirdparty/glad/glad.o`, make sure it exists, otherwise build it
if has_vroot && flag.contains('.o') {
if p.os == .msvc {
build_thirdparty_obj_file_with_msvc(flag)
}
else {
build_thirdparty_obj_file(flag)
}
}
p.table.flags << flag
return
}
if hash.starts_with('include') {
if p.first_run() && !is_sig {
p.cgen.includes << '#$hash'
return
}
}
else if hash.starts_with('typedef') {
if p.first_run() {
p.cgen.typedefs << '$hash'
}
}
// TODO remove after ui_mac.m is removed
else if hash.contains('embed') {
pos := hash.index('embed') + 5
file := hash.right(pos)
if p.pref.build_mode != BuildMode.default_mode {
p.genln('#include $file')
}
}
else if hash.contains('define') {
// Move defines on top
p.cgen.includes << '#$hash'
}
else if hash == 'v' {
println('v script')
//p.v_script = true
}
else {
if !p.can_chash {
p.error('bad token `#` (embedding C code is no longer supported)')
}
p.genln(hash)
}
}
fn (p mut Parser) if_st(is_expr bool, elif_depth int) string { fn (p mut Parser) if_st(is_expr bool, elif_depth int) string {
if is_expr { if is_expr {
if p.fileis('if_expr') { if p.fileis('if_expr') {
@ -3272,7 +3052,7 @@ fn (p mut Parser) switch_statement() {
} }
fn (p mut Parser) assert_statement() { fn (p mut Parser) assert_statement() {
if p.first_run() { if p.first_pass() {
return return
} }
p.check(.key_assert) p.check(.key_assert)
@ -3442,22 +3222,6 @@ fn (p mut Parser) js_decode() string {
return '' return ''
} }
fn is_compile_time_const(s string) bool {
s = s.trim_space()
if s == '' {
return false
}
if s.contains('\'') {
return true
}
for c in s {
if ! ((c >= `0` && c <= `9`) || c == `.`) {
return false
}
}
return true
}
/* /*
fn (p &Parser) building_v() bool { fn (p &Parser) building_v() bool {
cur_dir := os.getwd() cur_dir := os.getwd()
@ -3495,48 +3259,3 @@ fn (p mut Parser) defer_st() {
p.genln('*/') p.genln('*/')
} }
///////////////////////////////////////////////////////////////////////////////
// fmt helpers
fn (scanner mut Scanner) fgen(s string) {
if scanner.fmt_line_empty {
s = strings.repeat(`\t`, scanner.fmt_indent) + s
}
scanner.fmt_out.write(s)
scanner.fmt_line_empty = false
}
fn (scanner mut Scanner) fgenln(s string) {
if scanner.fmt_line_empty {
s = strings.repeat(`\t`, scanner.fmt_indent) + s
}
scanner.fmt_out.writeln(s)
scanner.fmt_line_empty = true
}
fn (p mut Parser) fgen(s string) {
p.scanner.fgen(s)
}
fn (p mut Parser) fspace() {
p.fgen(' ')
}
fn (p mut Parser) fgenln(s string) {
p.scanner.fgenln(s)
}
fn (p mut Parser) peek() Token {
for {
p.cgen.line = p.scanner.line_nr + 1
tok := p.scanner.peek()
if tok != .nl {
return tok
}
}
}
fn (p mut Parser) create_type_string(T Type, name string) {
p.scanner.create_type_string(T, name)
}

View File

@ -758,3 +758,4 @@ fn (s mut Scanner) create_type_string(T Type, name string) {
s.line_nr = line s.line_nr = line
s.inside_string = inside_string s.inside_string = inside_string
} }

View File

@ -5,12 +5,14 @@
module main module main
import math import math
import strings
struct Table { struct Table {
mut: mut:
types []Type types []Type
consts []Var consts []Var
fns map[string]Fn fns map[string]Fn
generic_fns []GenTable //map[string]GenTable // generic_fns['listen_and_serve'] == ['Blog', 'Forum']
obf_ids map[string]int // obf_ids['myfunction'] == 23 obf_ids map[string]int // obf_ids['myfunction'] == 23
packages []string // List of all modules registered by the application packages []string // List of all modules registered by the application
imports []string // List of all imports imports []string // List of all imports
@ -19,6 +21,11 @@ mut:
obfuscate bool obfuscate bool
} }
struct GenTable {
fn_name string
types []string
}
// Holds import information scoped to the parsed file // Holds import information scoped to the parsed file
struct FileImportTable { struct FileImportTable {
mut: mut:
@ -28,11 +35,11 @@ mut:
} }
enum AccessMod { enum AccessMod {
private // private imkey_mut private // private immutable
private_mut // private key_mut private_mut // private mutable
public // public immkey_mut (readonly) public // public immutable (readonly)
public_mut // public, but key_mut only in this module public_mut // public, but mutable only in this module
public_mut_mut // public and key_mut both inside and outside (not recommended to use, that's why it's so verbose) public_mut_mut // public and mutable both inside and outside (not recommended to use, that's why it's so verbose)
} }
struct Type { struct Type {
@ -47,6 +54,7 @@ mut:
is_interface bool is_interface bool
is_enum bool is_enum bool
enum_vals []string enum_vals []string
gen_types []string
// This field is used for types that are not defined yet but are known to exist. // This field is used for types that are not defined yet but are known to exist.
// It allows having things like `fn (f Foo) bar()` before `Foo` is defined. // It allows having things like `fn (f Foo) bar()` before `Foo` is defined.
// This information is needed in the first pass. // This information is needed in the first pass.
@ -98,6 +106,14 @@ fn (f Fn) str() string {
return '$f.name($str_args) $f.typ' return '$f.name($str_args) $f.typ'
} }
fn (t &Table) debug_fns() string {
mut s := strings.new_builder(1000)
for _, f in t.fns {
s.writeln(f.name)
}
return s.str()
}
// fn (types array_Type) print_to_file(f string) { // fn (types array_Type) print_to_file(f string) {
// } // }
const ( const (
@ -121,6 +137,8 @@ fn new_table(obfuscate bool) *Table {
mut t := &Table { mut t := &Table {
obf_ids: map[string]int{} obf_ids: map[string]int{}
fns: map[string]Fn{} fns: map[string]Fn{}
//generic_fns: map[string]GenTable{}
generic_fns: []GenTable
obfuscate: obfuscate obfuscate: obfuscate
} }
t.register_type('int') t.register_type('int')
@ -142,6 +160,7 @@ fn new_table(obfuscate bool) *Table {
t.register_type('bool') t.register_type('bool')
t.register_type('void') t.register_type('void')
t.register_type('voidptr') t.register_type('voidptr')
t.register_type('T')
t.register_type('va_list') t.register_type('va_list')
t.register_const('stdin', 'int', 'main', false) t.register_const('stdin', 'int', 'main', false)
t.register_const('stdout', 'int', 'main', false) t.register_const('stdout', 'int', 'main', false)
@ -169,6 +188,66 @@ fn (t mut Table) register_package(pkg string) {
t.packages << pkg t.packages << pkg
} }
fn (p mut Parser) register_import() {
if p.tok != .name {
p.error('bad import format')
}
mut pkg := p.lit.trim_space()
mut mod_alias := pkg
// submodule support
mut depth := 1
p.next()
for p.tok == .dot {
p.check(.dot)
submodule := p.check_name()
mod_alias = submodule
pkg += '.' + submodule
depth++
if depth > MaxModuleDepth {
p.error('module depth of $MaxModuleDepth exceeded: $pkg')
}
}
// aliasing (import encoding.base64 as b64)
if p.tok == .key_as && p.peek() == .name {
p.check(.key_as)
mod_alias = p.check_name()
}
// add import to file scope import table
p.import_table.register_alias(mod_alias, pkg)
// Make sure there are no duplicate imports
if p.table.imports.contains(pkg) {
return
}
p.log('adding import $pkg')
p.table.imports << pkg
p.table.register_package(pkg)
p.fgenln(' ' + pkg)
}
fn (p mut Parser) register_array(typ string) {
if typ.contains('*') {
println('bad arr $typ')
return
}
if !p.table.known_type(typ) {
p.register_type_with_parent(typ, 'array')
p.cgen.typedefs << 'typedef array $typ;'
}
}
fn (p mut Parser) register_map(typ string) {
if typ.contains('*') {
println('bad map $typ')
return
}
if !p.table.known_type(typ) {
p.register_type_with_parent(typ, 'map')
p.cgen.typedefs << 'typedef map $typ;'
}
}
fn (table &Table) known_pkg(pkg string) bool { fn (table &Table) known_pkg(pkg string) bool {
return pkg in table.packages return pkg in table.packages
} }
@ -679,6 +758,72 @@ fn is_valid_int_const(val, typ string) bool {
return true return true
} }
fn (t mut Table) register_generic_fn(fn_name string) {
t.generic_fns << GenTable{fn_name, []string}
}
fn (t mut Table) fn_gen_types(fn_name string) []string {
for _, f in t.generic_fns {
if f.fn_name == fn_name {
return f.types
}
}
}
// `foo<Bar>()`
// fn_name == 'foo'
// typ == 'Bar'
fn (t mut Table) register_generic_fn_type(fn_name, typ string) {
for i, f in t.generic_fns {
if f.fn_name == fn_name {
t.generic_fns[i].types << typ
return
}
}
}
fn (p mut Parser) typ_to_fmt(typ string, level int) string {
t := p.table.find_type(typ)
if t.is_enum {
return '%d'
}
switch typ {
case 'string': return '%.*s'
case 'ustring': return '%.*s'
case 'byte', 'int', 'char', 'byte', 'bool', 'u32', 'i32', 'i16', 'u16', 'i8', 'u8': return '%d'
case 'f64', 'f32': return '%f'
case 'i64', 'u64': return '%lld'
case 'byte*', 'byteptr': return '%s'
// case 'array_string': return '%s'
// case 'array_int': return '%s'
case 'void': p.error('cannot interpolate this value')
default:
if typ.ends_with('*') {
return '%p'
}
}
if t.parent != '' && level == 0 {
return p.typ_to_fmt(t.parent, level+1)
}
return ''
}
fn is_compile_time_const(s string) bool {
s = s.trim_space()
if s == '' {
return false
}
if s.contains('\'') {
return true
}
for c in s {
if ! ((c >= `0` && c <= `9`) || c == `.`) {
return false
}
}
return true
}
// Once we have a module format we can read from module file instead // Once we have a module format we can read from module file instead
// this is not optimal // this is not optimal
fn (table &Table) qualify_module(mod string, file_path string) string { fn (table &Table) qualify_module(mod string, file_path string) string {

54
compiler/vfmt.v Normal file
View File

@ -0,0 +1,54 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import strings
// fmt helpers
fn (scanner mut Scanner) fgen(s string) {
if scanner.fmt_line_empty {
s = strings.repeat(`\t`, scanner.fmt_indent) + s
}
scanner.fmt_out.write(s)
scanner.fmt_line_empty = false
}
fn (scanner mut Scanner) fgenln(s string) {
if scanner.fmt_line_empty {
s = strings.repeat(`\t`, scanner.fmt_indent) + s
}
scanner.fmt_out.writeln(s)
scanner.fmt_line_empty = true
}
fn (p mut Parser) fgen(s string) {
p.scanner.fgen(s)
}
fn (p mut Parser) fspace() {
p.fgen(' ')
}
fn (p mut Parser) fgenln(s string) {
p.scanner.fgenln(s)
}
fn (p mut Parser) peek() Token {
for {
tok := p.scanner.peek()
if tok != .nl {
return tok
}
}
}
fn (p mut Parser) fmt_inc() {
p.scanner.fmt_indent++
}
fn (p mut Parser) fmt_dec() {
p.scanner.fmt_indent--
}

View File

@ -13,7 +13,7 @@ struct Option {
// `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }` // `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }`
fn opt_ok(data voidptr, size int) Option { fn opt_ok(data voidptr, size int) Option {
if size > 255 { if size > 255 {
panic('option size too big: $size (max is 255)') panic('option size too big: $size (max is 255), this is a temporary limit')
} }
res := Option { res := Option {
ok: true ok: true

View File

@ -296,4 +296,4 @@ pub fn (d &Digest) size() int {
} }
} }
pub fn (d &Digest) block_size() int { return BlockSize } pub fn (d &Digest) block_size() int { return BlockSize }

View File

@ -189,11 +189,11 @@ pub fn (req &Request) do() Response {
} }
} }
fn unescape(s string) string { pub fn unescape(s string) string {
return string(byteptr(C.curl_unescape(s.str, s.len))) return string(byteptr(C.curl_unescape(s.str, s.len)))
} }
fn escape(s string) string { pub fn escape(s string) string {
return string(byteptr(C.curl_escape(s.str, s.len))) return string(byteptr(C.curl_escape(s.str, s.len)))
} }

View File

@ -248,4 +248,4 @@ pub fn range(arr []f64) f64 {
return f64(0) return f64(0)
} }
return max(arr) - min(arr) return max(arr) - min(arr)
} }

View File

@ -79,6 +79,10 @@ pub fn socket(family int, _type int, proto int) ?Socket {
} }
sockfd := C.socket(family, _type, proto) sockfd := C.socket(family, _type, proto)
one:=1
// This is needed so that there are no problems with reusing the
// same port after the application exits.
C.setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int))
if sockfd == 0 { if sockfd == 0 {
return error('socket: init failed') return error('socket: init failed')
} }
@ -117,11 +121,12 @@ pub fn (s Socket) bind(port int) ?int {
// put socket into passive mode and wait to receive // put socket into passive mode and wait to receive
pub fn (s Socket) listen() ?int { pub fn (s Socket) listen() ?int {
backlog := 128 backlog := 128
res := C.listen(s.sockfd, backlog) res := int(C.listen(s.sockfd, backlog))
if res < 0 { if res < 0 {
return error('socket: listen failed') return error('socket: listen failed')
} }
return int(res) println('liisten res = $res')
return res
} }
// put socket into passive mode with user specified backlog and wait to receive // put socket into passive mode with user specified backlog and wait to receive
@ -139,6 +144,7 @@ pub fn (s Socket) listen_backlog(backlog int) ?int {
// helper method to create, bind, and listen given port number // helper method to create, bind, and listen given port number
pub fn listen(port int) ?Socket { pub fn listen(port int) ?Socket {
println('net.listen($port)')
s := socket(AF_INET, SOCK_STREAM, 0) or { s := socket(AF_INET, SOCK_STREAM, 0) or {
return error(err) return error(err)
} }
@ -153,6 +159,7 @@ pub fn listen(port int) ?Socket {
// accept first connection request from socket queue // accept first connection request from socket queue
pub fn (s Socket) accept() ?Socket { pub fn (s Socket) accept() ?Socket {
println('accept()')
addr := C.sockaddr_storage{} addr := C.sockaddr_storage{}
size := 128 // sizeof(sockaddr_storage) size := 128 // sizeof(sockaddr_storage)
sockfd := C.accept(s.sockfd, &addr, &size) sockfd := C.accept(s.sockfd, &addr, &size)
@ -251,3 +258,47 @@ pub fn (s Socket) close() ?int {
return 0 return 0
} }
const (
MAX_READ = 400
)
pub fn (s Socket) write(str string) {
line := '$str\r\n'
C.write(s.sockfd, line.str, line.len)
}
pub fn (s Socket) read_line() string {
mut res := ''
for {
println('.')
mut buf := malloc(MAX_READ)
n := int(C.recv(s.sockfd, buf, MAX_READ-1, 0))
println('numbytes=$n')
if n == -1 {
println('recv failed')
// TODO
return ''
}
if n == 0 {
break
}
// println('resp len=$numbytes')
buf[n] = `\0`
// C.printf('!!buf= "%s" n=%d\n', buf,n)
line := string(buf)
res += line
// Reached a newline. That's an end of an IRC message
// TODO dont need ends_with check ?
if line.ends_with('\n') || n < MAX_READ - 1 {
// println('NL')
break
}
if line.ends_with('\r\n') {
// println('RNL')
break
}
}
return res
}

View File

@ -14,4 +14,4 @@ pub fn get_error_msg(code int) string {
return '' return ''
} }
return tos(_ptr_text, C.strlen(_ptr_text)) return tos(_ptr_text, C.strlen(_ptr_text))
} }

View File

@ -90,4 +90,4 @@ pub fn get_error_msg(code int) string {
return '' return ''
} }
return tos(_ptr_text, C.strlen(_ptr_text)) return tos(_ptr_text, C.strlen(_ptr_text))
} }

View File

@ -56,10 +56,110 @@ pub fn random() Time {
} }
} }
pub fn unix(u int) Time { const (
mut t := &C.tm{!} // The unsigned zero year for internal calculations.
t = C.localtime(&u) // Must be 1 mod 400, and times before it will not compute correctly,
return convert_ctime(t) // but otherwise can be changed at will.
absoluteZeroYear = i64(-292277022399)
secondsPerMinute = 60
secondsPerHour = 60 * secondsPerMinute
secondsPerDay = 24 * secondsPerHour
secondsPerWeek = 7 * secondsPerDay
daysPer400Years = 365*400 + 97
daysPer100Years = 365*100 + 24
daysPer4Years = 365*4 + 1
daysBefore = [
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
]
)
// Based on Go's time package.
// Copyright 2009 The Go Authors.
pub fn unix(abs int) Time {
// Split into time and day.
mut d := abs / secondsPerDay
// Account for 400 year cycles.
mut n := d / daysPer400Years
mut y := 400 * n
d -= daysPer400Years * n
// Cut off 100-year cycles.
// The last cycle has one extra leap year, so on the last day
// of that year, day / daysPer100Years will be 4 instead of 3.
// Cut it back down to 3 by subtracting n>>2.
n = d / daysPer100Years
n -= n >> 2
y += 100 * n
d -= daysPer100Years * n
// Cut off 4-year cycles.
// The last cycle has a missing leap year, which does not
// affect the computation.
n = d / daysPer4Years
y += 4 * n
d -= daysPer4Years * n
// Cut off years within a 4-year cycle.
// The last year is a leap year, so on the last day of that year,
// day / 365 will be 4 instead of 3. Cut it back down to 3
// by subtracting n>>2.
n = d / 365
n -= n >> 2
y += n
d -= 365 * n
yday := int(d)
mut day := yday
year := abs / int(3.154e+7) + 1970 //int(i64(y) + absoluteZeroYear)
hour := int(abs%secondsPerDay) / secondsPerHour
minute := int(abs % secondsPerHour) / secondsPerMinute
second := int(abs % secondsPerMinute)
if is_leap_year(year) {
// Leap year
if day > 31+29-1 {
// After leap day; pretend it wasn't there.
day--
} else if day == 31+29-1 {
// Leap day.
day = 29
return Time{year:year, month:2, day:day, hour:hour, minute: minute, second: second}
}
}
// Estimate month on assumption that every month has 31 days.
// The estimate may be too low by at most one month, so adjust.
mut month := day / 31
mut begin := 0
end := int(daysBefore[month+1])
if day >= end {
month++
begin = end
} else {
begin = int(daysBefore[month])
}
month++ // because January is 1
day = day - begin + 1
return Time{year:year, month: month, day:day, hour:hour, minute: minute, second: second}
} }
pub fn convert_ctime(t tm) Time { pub fn convert_ctime(t tm) Time {

View File

@ -38,3 +38,16 @@ fn test_days_in_month() {
assert check(11, 2001, 30) // November assert check(11, 2001, 30) // November
assert check(12, 2001, 31) // December assert check(12, 2001, 31) // December
} }
fn test_unix() {
t := time.unix(1564366499)
assert t.year == 2019
assert t.month == 7
assert t.day == 29
assert t.hour == 2
assert t.minute == 14
//assert t.second == 32 // TODO broken
}

69
vlib/vweb/tmpl/tmpl.v Normal file
View File

@ -0,0 +1,69 @@
module tmpl
import os
import strings
const (
STR_START = 'sb.write(\''
STR_END = '\' ) '
)
pub fn compile_template(path string) string {
//lines := os.read_lines(path)
mut html := os.read_file(path) or {
panic('html failed')
return ''
}
mut header := ''
if os.file_exists('header.html') {
h := os.read_file('header.html') or {
panic('html failed')
return ''
}
header = h
}
lines := html.split_into_lines()
mut s := strings.new_builder(1000)
base := path.all_after('/').replace('.html', '')
s.writeln('module main import strings fn ${base}_view() string { // this line will get removed becase only function body is embedded
mut sb := strings.new_builder(${lines.len * 30})
header := \'$header\'
_ := header
//footer := \'footer\'
')
s.writeln(STR_START)
for line in lines {
if line.contains('@if ') {
s.writeln(STR_END)
pos := line.index('@if')
s.writeln('if ' + line.right(pos + 4) + '{')
s.writeln(STR_START)
}
else if line.contains('@end') {
s.writeln(STR_END)
s.writeln('}')
s.writeln(STR_START)
}
else if line.contains('@else') {
s.writeln(STR_END)
s.writeln(' } else { ')
s.writeln(STR_START)
}
else if line.contains('@for') {
s.writeln(STR_END)
pos := line.index('@for')
s.writeln('for ' + line.right(pos + 4) + '{')
s.writeln(STR_START)
}
// @name
else {
s.writeln(line.replace('@', '\x24').replace('\'', '"') )
}
}
s.writeln(STR_END)
s.writeln('tmpl_res := sb.str() ')
s.writeln('return tmpl_res }')
return s.str()
}

121
vlib/vweb/vweb.v Normal file
View File

@ -0,0 +1,121 @@
module vweb
import (
os
strings
net
http
)
struct Context {
pub:
req http.Request
conn net.Socket
post_form map[string]string
// TODO Response
headers []string
}
pub fn (ctx Context) write(s string) {
//ctx.conn.write(s)
}
pub fn (ctx Context) redirect(url string) {
h := ctx.headers.join('\n')
ctx.conn.write('
HTTP/1.1 302 Found
Location: $url
$h
')
}
pub fn (ctx mut Context) set_cookie(key, val string) {
ctx.set_header('Set-Cookie', '$key=$val')
}
pub fn (ctx Context) get_cookie(key string) string {
cookie := ctx.req.headers['Cookie']
return cookie.find_between('$key=', ';')
}
fn (ctx mut Context) set_header(key, val string) {
// ctx.resp.headers[key] = val
ctx.headers << '$key: $val'
}
pub fn (ctx Context) html(html string) {
//tmpl := os.read_file(path) or {return}
ctx.conn.write('HTTP/1.1 200 OK
Content-Type: text/html
$html
')
}
pub fn run<T>(port int) {
l := net.listen(port) or { panic('failed to listen') return }
for {
conn := l.accept() or {
panic('accept() failed')
return
}
// TODO move this to handle_conn<T>(conn, app)
s := conn.read_line()
// Parse the first line
// "GET / HTTP/1.1"
first_line := s.all_before('\n')
vals := first_line.split(' ')
mut action := vals[1].right(1).all_before('/')
if action == '' {
action = 'index'
}
req := http.Request{
headers: map[string]string{}
ws_func: 0
user_ptr: 0
method: vals[0]
url: vals[1]
}
mut app := T{
vweb: Context{
req: req
conn: conn
post_form: map[string]string{}
}
}
app.init()
if req.method == 'POST' {
app.vweb.parse_form(s)
}
println('vweb action = "$action"')
if vals.len < 2 {
println('no vals for http')
return
}
app.$action()
conn.close()
}
}
fn (ctx mut Context) parse_form(s string) {
if ctx.req.method != 'POST' {
return
}
pos := s.index('\r\n\r\n')
if pos > -1 {
mut str_form := s.substr(pos, s.len)
str_form = str_form.replace('+', ' ')
words := str_form.split('&')
for word in words {
keyval := word.split('=')
key := keyval[0]
val := keyval[1]
//println('http form $key => $val')
ctx.post_form[key] = http.unescape(val)
}
}
}