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:
parent
f1373874ef
commit
207bab5f79
@ -20,11 +20,11 @@ struct CGen {
|
||||
fns []string
|
||||
so_fns []string
|
||||
consts_init []string
|
||||
lines []string
|
||||
//buf strings.Builder
|
||||
is_user bool
|
||||
mut:
|
||||
run Pass
|
||||
lines []string
|
||||
pass Pass
|
||||
nogen bool
|
||||
tmp_line string
|
||||
cur_line string
|
||||
@ -53,7 +53,7 @@ fn new_cgen(out_name_c string) *CGen {
|
||||
}
|
||||
|
||||
fn (g mut CGen) genln(s string) {
|
||||
if g.nogen || g.run != .main {
|
||||
if g.nogen || g.pass != .main {
|
||||
return
|
||||
}
|
||||
if g.is_tmp {
|
||||
@ -72,7 +72,7 @@ fn (g mut CGen) genln(s string) {
|
||||
}
|
||||
|
||||
fn (g mut CGen) gen(s string) {
|
||||
if g.nogen || g.run != .main {
|
||||
if g.nogen || g.pass != .main {
|
||||
return
|
||||
}
|
||||
if g.is_tmp {
|
||||
@ -84,7 +84,7 @@ fn (g mut CGen) gen(s string) {
|
||||
}
|
||||
|
||||
fn (g mut CGen) resetln(s string) {
|
||||
if g.nogen || g.run != .main {
|
||||
if g.nogen || g.pass != .main {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
if g.nogen || g.run != .main {
|
||||
if g.nogen || g.pass != .main {
|
||||
return
|
||||
}
|
||||
// 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) {
|
||||
if g.nogen || g.run != .main {
|
||||
if g.nogen || g.pass != .main {
|
||||
return
|
||||
}
|
||||
if g.is_tmp {
|
||||
@ -219,21 +219,21 @@ fn (p mut Parser) print_prof_counters() string {
|
||||
}
|
||||
|
||||
fn (p mut Parser) gen_type(s string) {
|
||||
if !p.first_run() {
|
||||
if !p.first_pass() {
|
||||
return
|
||||
}
|
||||
p.cgen.types << s
|
||||
}
|
||||
|
||||
fn (p mut Parser) gen_typedef(s string) {
|
||||
if !p.first_run() {
|
||||
if !p.first_pass() {
|
||||
return
|
||||
}
|
||||
p.cgen.typedefs << s
|
||||
}
|
||||
|
||||
fn (p mut Parser) gen_type_alias(s string) {
|
||||
if !p.first_run() {
|
||||
if !p.first_pass() {
|
||||
return
|
||||
}
|
||||
p.cgen.type_aliases << s
|
||||
|
203
compiler/comptime.v
Normal file
203
compiler/comptime.v
Normal 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)
|
||||
}
|
||||
|
142
compiler/fn.v
142
compiler/fn.v
@ -30,6 +30,7 @@ mut:
|
||||
returns_error bool
|
||||
is_decl bool // type myfn fn(int, int)
|
||||
defer_text string
|
||||
//gen_types []string
|
||||
}
|
||||
|
||||
fn (f &Fn) find_var(name string) Var {
|
||||
@ -110,7 +111,7 @@ fn (p mut Parser) fn_decl() {
|
||||
defer { p.fgenln('\n') }
|
||||
is_pub := p.tok == .key_pub
|
||||
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')
|
||||
}
|
||||
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)')
|
||||
}
|
||||
// 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('p.mod=$p.mod')
|
||||
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 {
|
||||
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)
|
||||
// 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 {
|
||||
@ -213,13 +214,19 @@ fn (p mut Parser) fn_decl() {
|
||||
// Generic?
|
||||
mut is_generic := false
|
||||
if p.tok == .lt {
|
||||
is_generic = true
|
||||
p.next()
|
||||
gen_type := p.check_name()
|
||||
if gen_type != 'T' {
|
||||
p.error('only `T` is allowed as a generic type for now')
|
||||
}
|
||||
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 (...)
|
||||
p.fn_args(mut f)
|
||||
@ -247,14 +254,13 @@ fn (p mut Parser) fn_decl() {
|
||||
p.fgen(' ')
|
||||
p.check(.lcbr)
|
||||
}
|
||||
// Register option ? type
|
||||
// Register ?option type
|
||||
if typ.starts_with('Option_') {
|
||||
p.cgen.typedefs << 'typedef Option $typ;'
|
||||
}
|
||||
// Register function
|
||||
f.typ = typ
|
||||
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
|
||||
if f.name == 'main' && !has_receiver {
|
||||
if str_args != '' || typ != 'void' {
|
||||
@ -263,21 +269,19 @@ fn (p mut Parser) fn_decl() {
|
||||
typ = 'int'
|
||||
str_args = 'int argc, char** argv'
|
||||
}
|
||||
|
||||
mut dll_export_linkage := ''
|
||||
|
||||
if p.os == .msvc && p.attr == 'live' && p.pref.is_so {
|
||||
dll_export_linkage = '__declspec(dllexport) '
|
||||
dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so {
|
||||
'__declspec(dllexport) '
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
// Only in C code generate User_register() instead of register()
|
||||
// if p.file_name != '.vwebtmpl.v' {
|
||||
// oif !p.name.ends_with('_vwebview') {
|
||||
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
|
||||
// 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)
|
||||
// Start generation of the function body
|
||||
skip_main_in_test := f.name == 'main' && p.pref.is_test
|
||||
@ -285,8 +289,28 @@ fn (p mut Parser) fn_decl() {
|
||||
if p.pref.obfuscate {
|
||||
p.genln('; // $f.name')
|
||||
}
|
||||
// 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 {
|
||||
p.genln('$typ $fn_name_cgen($str_args);')
|
||||
p.fgenln('')
|
||||
@ -294,13 +318,12 @@ fn (p mut Parser) fn_decl() {
|
||||
if is_c {
|
||||
p.fgenln('\n')
|
||||
}
|
||||
p.cur_fn = f
|
||||
// Register the method
|
||||
if receiver_typ != '' {
|
||||
mut receiver_t := p.table.find_type(receiver_typ)
|
||||
// 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.
|
||||
if p.first_run() && receiver_t.name == '' {
|
||||
if p.first_pass() && receiver_t.name == '' {
|
||||
// println('fn decl !!!!!!! REG PH $receiver_typ')
|
||||
p.table.register_type2(Type {
|
||||
name: receiver_typ.replace('*', '')
|
||||
@ -315,8 +338,9 @@ fn (p mut Parser) fn_decl() {
|
||||
// println('register_fn typ=$typ isg=$is_generic')
|
||||
p.table.register_fn(f)
|
||||
}
|
||||
if is_sig || p.first_run() || is_live || is_fn_header || skip_main_in_test {
|
||||
// First pass? Skip the body for now [BIG]
|
||||
if is_sig || p.first_pass() || is_live || is_fn_header || skip_main_in_test {
|
||||
// First pass? Skip the body for now
|
||||
// Look for generic calls.
|
||||
if !is_sig && !is_fn_header {
|
||||
mut opened_scopes := 0
|
||||
mut closed_scopes := 0
|
||||
@ -328,6 +352,20 @@ fn (p mut Parser) fn_decl() {
|
||||
closed_scopes++
|
||||
}
|
||||
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) {
|
||||
break
|
||||
}
|
||||
@ -340,18 +378,18 @@ fn (p mut Parser) fn_decl() {
|
||||
}
|
||||
}
|
||||
// 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')
|
||||
p.cgen.so_fns << 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)'
|
||||
if p.pref.obfuscate {
|
||||
fn_decl += '; // ${f.name}'
|
||||
fn_decl += '; // $f.name'
|
||||
}
|
||||
// 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
|
||||
if f.name == 'darwin__nsstring' && p.pref.build_mode == .default_mode {
|
||||
return
|
||||
@ -360,12 +398,10 @@ fn (p mut Parser) fn_decl() {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if p.attr == 'live' && p.pref.is_so {
|
||||
//p.genln('// live_function body start')
|
||||
p.genln('pthread_mutex_lock(&live_fn_mutex);')
|
||||
}
|
||||
|
||||
if f.name == 'main' || f.name == 'WinMain' {
|
||||
p.genln('init_consts();')
|
||||
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()}')
|
||||
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' {
|
||||
p.genln('double _PROF_START = time__ticks();//$f.name')
|
||||
cgen_name := p.table.cgen_name(f)
|
||||
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.cgen.nogen = false
|
||||
// Print counting result after all statements in main
|
||||
if p.pref.is_prof && f.name == 'main' {
|
||||
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' {
|
||||
p.error('$f.name must return "$typ"')
|
||||
}
|
||||
|
||||
if p.attr == 'live' && p.pref.is_so {
|
||||
//p.genln('// live_function body end')
|
||||
p.genln('pthread_mutex_unlock(&live_fn_mutex);')
|
||||
}
|
||||
|
||||
// {} closed correctly? scope_level should be 0
|
||||
if p.mod == 'main' {
|
||||
// println(p.cur_fn.scope_level)
|
||||
@ -436,13 +475,17 @@ _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)
|
||||
// if p.builtin_pkg || p.mod == 'os' ||p.mod=='http'{
|
||||
if p.mod != 'main' {
|
||||
if !is_generic {
|
||||
p.genln('}')
|
||||
}
|
||||
return
|
||||
}
|
||||
p.check_unused_variables()
|
||||
p.cur_fn = EmptyFn
|
||||
if !is_generic {
|
||||
p.genln('}')
|
||||
}
|
||||
}
|
||||
|
||||
fn (p mut Parser) check_unused_variables() {
|
||||
for var in p.cur_fn.local_vars {
|
||||
@ -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) {
|
||||
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 {
|
||||
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()`')
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// 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.next()
|
||||
p.fn_call_args(f)
|
||||
// foo<Bar>()
|
||||
p.fn_call_args(mut f)
|
||||
p.gen(')')
|
||||
p.calling_c = false
|
||||
// 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`')
|
||||
}
|
||||
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')
|
||||
}
|
||||
if is_mut {
|
||||
@ -678,7 +742,7 @@ fn (p mut Parser) fn_args(f mut Fn) {
|
||||
}
|
||||
|
||||
// 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('(')
|
||||
// println('fn_call_args() name=$f.name args.len=$f.args.len')
|
||||
// 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.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.next()
|
||||
return p.fn_call_args(f)
|
||||
return p.fn_call_args(mut f)
|
||||
}
|
||||
p.error(error_msg)
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ fn (p mut Parser) gen_json_for_type(typ Type) {
|
||||
if t == 'int' || t == 'string' || t == 'bool' {
|
||||
return
|
||||
}
|
||||
if p.first_run() {
|
||||
if p.first_pass() {
|
||||
return
|
||||
}
|
||||
// println('gen_json_for_type( $typ.name )')
|
||||
|
@ -179,7 +179,7 @@ fn (v mut V) compile() {
|
||||
p.parse()
|
||||
}
|
||||
// Main pass
|
||||
cgen.run = Pass.main
|
||||
cgen.pass = Pass.main
|
||||
if v.pref.is_play {
|
||||
cgen.genln('#define VPLAY (1) ')
|
||||
}
|
||||
|
@ -4,9 +4,11 @@
|
||||
|
||||
module main
|
||||
|
||||
import os
|
||||
import rand
|
||||
import strings
|
||||
import (
|
||||
os
|
||||
rand
|
||||
strings
|
||||
)
|
||||
|
||||
struct Var {
|
||||
mut:
|
||||
@ -35,6 +37,7 @@ struct Parser {
|
||||
file_path string // "/home/user/hello.v"
|
||||
file_name string // "hello.v"
|
||||
mut:
|
||||
v *V
|
||||
scanner *Scanner
|
||||
// tokens []Token // TODO cache all tokens, right now they have to be scanned twice
|
||||
token_idx int
|
||||
@ -45,7 +48,7 @@ mut:
|
||||
cgen *CGen
|
||||
table *Table
|
||||
import_table *FileImportTable // Holds imports for just the file being parsed
|
||||
run Pass // TODO rename `run` to `pass`
|
||||
pass Pass
|
||||
os OS
|
||||
mod string
|
||||
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
|
||||
building_v bool
|
||||
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 (
|
||||
@ -84,29 +89,30 @@ const (
|
||||
MaxModuleDepth = 4
|
||||
)
|
||||
|
||||
fn (c mut V) new_parser(path string, run Pass) Parser {
|
||||
c.log('new_parser("$path")')
|
||||
c.cgen.run = run
|
||||
fn (v mut V) new_parser(path string, pass Pass) Parser {
|
||||
v.log('new_parser("$path")')
|
||||
v.cgen.pass = pass
|
||||
mut p := Parser {
|
||||
v: v
|
||||
file_path: path
|
||||
file_name: path.all_after('/')
|
||||
scanner: new_scanner(path)
|
||||
table: c.table
|
||||
table: v.table
|
||||
import_table: new_file_import_table(path)
|
||||
cur_fn: EmptyFn
|
||||
cgen: c.cgen
|
||||
is_script: (c.pref.is_script && path == c.dir)
|
||||
pref: c.pref
|
||||
os: c.os
|
||||
run: run
|
||||
vroot: c.vroot
|
||||
building_v: !c.pref.is_repl && (path.contains('compiler/') ||
|
||||
cgen: v.cgen
|
||||
is_script: (v.pref.is_script && path == v.dir)
|
||||
pref: v.pref
|
||||
os: v.os
|
||||
pass: pass
|
||||
vroot: v.vroot
|
||||
building_v: !v.pref.is_repl && (path.contains('compiler/') ||
|
||||
path.contains('v/vlib'))
|
||||
|
||||
}
|
||||
|
||||
c.cgen.line_directives = c.pref.is_debuggable
|
||||
c.cgen.file = path
|
||||
v.cgen.line_directives = v.pref.is_debuggable
|
||||
v.cgen.file = path
|
||||
|
||||
p.next()
|
||||
// p.scanner.debug_tokens()
|
||||
@ -132,7 +138,7 @@ fn (p &Parser) log(s string) {
|
||||
}
|
||||
|
||||
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
|
||||
if p.is_script || p.pref.is_test {
|
||||
p.mod = 'main'
|
||||
@ -158,7 +164,7 @@ fn (p mut Parser) parse() {
|
||||
p.table.register_package(fq_mod)
|
||||
// replace "." with "_dot_" in module name for C variable names
|
||||
p.mod = fq_mod.replace('.', '_dot_')
|
||||
if p.run == .imports {
|
||||
if p.pass == .imports {
|
||||
for p.tok == .key_import && p.peek() != .key_const {
|
||||
p.imports()
|
||||
}
|
||||
@ -256,7 +262,7 @@ fn (p mut Parser) parse() {
|
||||
p.cur_fn = MainFn
|
||||
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 {
|
||||
panic('failed to create fmt.v')
|
||||
return
|
||||
@ -270,7 +276,7 @@ fn (p mut Parser) parse() {
|
||||
if p.is_script && !p.pref.is_test {
|
||||
// cur_fn is empty since there was no fn main declared
|
||||
// we need to set it to save and find variables
|
||||
if p.first_run() {
|
||||
if p.first_pass() {
|
||||
if p.cur_fn.name == '' {
|
||||
p.cur_fn = MainFn
|
||||
}
|
||||
@ -320,43 +326,6 @@ fn (p mut Parser) imports() {
|
||||
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() {
|
||||
is_import := p.tok == .key_import
|
||||
p.inside_const = true
|
||||
@ -371,9 +340,9 @@ fn (p mut Parser) const_decl() {
|
||||
for p.tok == .name {
|
||||
// `Age = 20`
|
||||
mut name := p.check_name()
|
||||
if p.pref.is_play && ! (name[0] >= `A` && name[0] <= `Z`) {
|
||||
p.error('const name must be capitalized')
|
||||
}
|
||||
//if ! (name[0] >= `A` && name[0] <= `Z`) {
|
||||
//p.error('const name must be capitalized')
|
||||
//}
|
||||
// Imported consts (like GL_TRIANG.leS) dont need pkg prepended (gl__GL_TRIANG.leS)
|
||||
if !is_import {
|
||||
name = p.prepend_pkg(name)
|
||||
@ -383,11 +352,11 @@ fn (p mut Parser) const_decl() {
|
||||
p.check_space(.assign)
|
||||
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.table.register_const(name, typ, p.mod, is_import)
|
||||
if p.run == .main && !is_import {
|
||||
if p.pass == .main && !is_import {
|
||||
// TODO hack
|
||||
// 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
|
||||
@ -437,7 +406,7 @@ fn (p mut Parser) interface_method(field_name, receiver string) &Fn {
|
||||
is_method: true
|
||||
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)
|
||||
if p.scanner.has_gone_over_line_end() {
|
||||
method.typ = 'void'
|
||||
@ -488,7 +457,7 @@ fn (p mut Parser) struct_decl() {
|
||||
if !is_c && !p.builtin_pkg && p.mod != 'main' {
|
||||
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')
|
||||
}
|
||||
// Generate type definitions
|
||||
@ -543,7 +512,7 @@ fn (p mut Parser) struct_decl() {
|
||||
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
|
||||
for p.tok != .rcbr {
|
||||
@ -616,7 +585,7 @@ fn (p mut Parser) struct_decl() {
|
||||
p.fgenln('')
|
||||
}
|
||||
|
||||
if !is_ph && p.first_run() {
|
||||
if !is_ph && p.first_pass() {
|
||||
p.table.register_type2(typ)
|
||||
//println('registering 1 nrfields=$typ.fields.len')
|
||||
}
|
||||
@ -654,7 +623,7 @@ fn (p mut Parser) enum_decl(_enum_name string) {
|
||||
fields << field
|
||||
p.fgenln('')
|
||||
name := '${p.mod}__${enum_name}_$field'
|
||||
if p.run == .main {
|
||||
if p.pass == .main {
|
||||
p.cgen.consts << '#define $name $val'
|
||||
}
|
||||
if p.tok == .comma {
|
||||
@ -738,19 +707,14 @@ if p.scanner.line_comment != '' {
|
||||
|
||||
fn (p mut Parser) error(s string) {
|
||||
// Dump all vars and types for debugging
|
||||
if false {
|
||||
//file_types := os.create('$TmpPath/types')
|
||||
//file_vars := os.create('$TmpPath/vars')
|
||||
// ////debug("ALL T", q.J(p.table.types))
|
||||
if p.pref.is_debug {
|
||||
// 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))
|
||||
//file_types.close()
|
||||
//file_vars.close()
|
||||
}
|
||||
if p.pref.is_verbose {
|
||||
println('pass=$p.run fn=`$p.cur_fn.name`')
|
||||
os.write_file('fns.txt', p.table.debug_fns())
|
||||
}
|
||||
//if p.pref.is_verbose {
|
||||
println('pass=$p.pass fn=`$p.cur_fn.name`')
|
||||
//}
|
||||
p.cgen.save()
|
||||
// V git pull hint
|
||||
cur_path := os.getwd()
|
||||
@ -769,8 +733,8 @@ fn (p mut Parser) error(s string) {
|
||||
p.scanner.error(s.replace('array_', '[]').replace('__', '.').replace('Option_', '?'))
|
||||
}
|
||||
|
||||
fn (p &Parser) first_run() bool {
|
||||
return p.run == .decl
|
||||
fn (p &Parser) first_pass() bool {
|
||||
return p.pass == .decl
|
||||
}
|
||||
|
||||
// TODO return Type instead of string?
|
||||
@ -890,13 +854,13 @@ fn (p mut Parser) get_type() string {
|
||||
mut t := p.table.find_type(typ)
|
||||
// "typ" not found? try "pkg__typ"
|
||||
if t.name == '' && !p.builtin_pkg {
|
||||
// && !p.first_run() {
|
||||
// && !p.first_pass() {
|
||||
if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') &&
|
||||
!typ.starts_with('[') {
|
||||
typ = p.prepend_pkg(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('all registered types:')
|
||||
// for q in p.table.types {
|
||||
@ -919,7 +883,7 @@ fn (p mut Parser) get_type() string {
|
||||
}
|
||||
else if is_arr {
|
||||
typ = 'array_$typ'
|
||||
// p.log('ARR TYPE="$typ" run=$p.run')
|
||||
// p.log('ARR TYPE="$typ" run=$p.pass')
|
||||
// We come across "[]User" etc ?
|
||||
p.register_array(typ)
|
||||
}
|
||||
@ -1050,14 +1014,6 @@ fn (p mut Parser) vh_genln(s string) {
|
||||
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 {
|
||||
p.cgen.is_tmp = false
|
||||
tok := p.tok
|
||||
@ -1342,13 +1298,17 @@ fn (p mut Parser) bterm() string {
|
||||
|
||||
// also called on *, &, @, . (enum)
|
||||
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:')
|
||||
// println(p.table.known_type(p.lit))
|
||||
// hack for struct_init TODO
|
||||
/*
|
||||
hack_pos := p.scanner.pos
|
||||
hack_tok := p.tok
|
||||
hack_lit := p.lit
|
||||
*/
|
||||
ph := p.cgen.add_placeholder()
|
||||
// amp
|
||||
ptr := p.tok == .amp
|
||||
@ -1379,7 +1339,7 @@ fn (p mut Parser) name_expr() string {
|
||||
}
|
||||
// enum value? (`color == .green`)
|
||||
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)
|
||||
if T.is_enum {
|
||||
p.check(.dot)
|
||||
@ -1445,7 +1405,7 @@ fn (p mut Parser) name_expr() string {
|
||||
}
|
||||
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)
|
||||
if p.table.known_type(name) {
|
||||
// float(5), byte(0), (*int)(ptr) etc
|
||||
@ -1480,16 +1440,24 @@ fn (p mut Parser) name_expr() string {
|
||||
}
|
||||
else if p.peek() == .lcbr {
|
||||
// go back to name start (pkg.name)
|
||||
/*
|
||||
p.scanner.pos = hack_pos
|
||||
p.tok = hack_tok
|
||||
p.lit = hack_lit
|
||||
*/
|
||||
// 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));
|
||||
if is_c_struct_init {
|
||||
p.is_c_struct_init = true
|
||||
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
|
||||
@ -1529,7 +1497,7 @@ fn (p mut Parser) name_expr() string {
|
||||
mut f := p.table.find_fn(name)
|
||||
if f.name == '' {
|
||||
// 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.
|
||||
if p.v_script {
|
||||
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
|
||||
// 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.next()
|
||||
return 'void*'
|
||||
@ -1564,6 +1533,9 @@ fn (p mut Parser) name_expr() string {
|
||||
// p.error('`$f.name` used as value')
|
||||
}
|
||||
p.log('calling function')
|
||||
if p.fileis('vtalk') {
|
||||
//println('calling fn $f.name')
|
||||
}
|
||||
p.fn_call(f, 0, '', '')
|
||||
// dot after a function call: `get_user().age`
|
||||
if p.tok == .dot {
|
||||
@ -1593,7 +1565,7 @@ fn (p mut Parser) var_expr(v Var) string {
|
||||
//p.print_tok()
|
||||
T := p.table.find_type(typ)
|
||||
p.gen('(')
|
||||
p.fn_call_args(T.func)
|
||||
p.fn_call_args(mut T.func)
|
||||
p.gen(')')
|
||||
typ = T.func.typ
|
||||
}
|
||||
@ -1601,7 +1573,6 @@ fn (p mut Parser) var_expr(v Var) string {
|
||||
// users[0].name
|
||||
if p.tok == .lsbr {
|
||||
typ = p.index_expr(typ, fn_ph)
|
||||
// ////println('QQQQ KEK $typ')
|
||||
}
|
||||
// a.b.c().d chain
|
||||
// mut dc := 0
|
||||
@ -1651,6 +1622,7 @@ fn (p mut Parser) var_expr(v Var) string {
|
||||
return typ
|
||||
}
|
||||
|
||||
// for debugging only
|
||||
fn (p &Parser) fileis(s string) bool {
|
||||
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`
|
||||
fn (p mut Parser) dot(str_typ string, method_ph int) string {
|
||||
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
|
||||
p.fgen(field_name)
|
||||
p.log('dot() field_name=$field_name typ=$str_typ')
|
||||
//if p.fileis('main.v') {
|
||||
//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_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_') {
|
||||
opt_type := typ.name.right(7)
|
||||
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
|
||||
fn (p mut Parser) expression() string {
|
||||
if p.scanner.file_path.contains('test_test') {
|
||||
println('expression() pass=$p.run tok=')
|
||||
println('expression() pass=$p.pass tok=')
|
||||
p.print_tok()
|
||||
}
|
||||
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 {
|
||||
str = str.replace('"', '\\"')
|
||||
$if windows {
|
||||
@ -2546,7 +2496,7 @@ fn (p mut Parser) array_init() string {
|
||||
if is_fixed_size {
|
||||
p.next()
|
||||
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:
|
||||
// `a = {1,2,3}`, not `a = (int[]) {1,2,3}`
|
||||
if p.inside_const {
|
||||
@ -2567,8 +2517,8 @@ fn (p mut Parser) array_init() string {
|
||||
}
|
||||
p.gen(' })')
|
||||
// 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
|
||||
if !p.first_run() {
|
||||
// TODO why need !first_pass()?? Otherwise it goes to the very top of the out.c file
|
||||
if !p.first_pass() {
|
||||
if i == 0 {
|
||||
p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) {EMPTY_STRUCT_INIT ')
|
||||
} else {
|
||||
@ -2580,32 +2530,9 @@ fn (p mut Parser) array_init() string {
|
||||
return typ
|
||||
}
|
||||
|
||||
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 (p mut Parser) struct_init(is_c_struct_init bool) string {
|
||||
fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string {
|
||||
p.is_struct_init = true
|
||||
mut typ := p.get_type()
|
||||
p.next()
|
||||
p.scanner.fmt_out.cut(typ.len)
|
||||
ptr := typ.contains('*')
|
||||
// TODO tm struct struct bug
|
||||
@ -2810,153 +2737,6 @@ fn os_name_to_ifdef(name string) string {
|
||||
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 {
|
||||
if is_expr {
|
||||
if p.fileis('if_expr') {
|
||||
@ -3272,7 +3052,7 @@ fn (p mut Parser) switch_statement() {
|
||||
}
|
||||
|
||||
fn (p mut Parser) assert_statement() {
|
||||
if p.first_run() {
|
||||
if p.first_pass() {
|
||||
return
|
||||
}
|
||||
p.check(.key_assert)
|
||||
@ -3442,22 +3222,6 @@ fn (p mut Parser) js_decode() string {
|
||||
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 {
|
||||
cur_dir := os.getwd()
|
||||
@ -3495,48 +3259,3 @@ fn (p mut Parser) defer_st() {
|
||||
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)
|
||||
}
|
||||
|
@ -758,3 +758,4 @@ fn (s mut Scanner) create_type_string(T Type, name string) {
|
||||
s.line_nr = line
|
||||
s.inside_string = inside_string
|
||||
}
|
||||
|
||||
|
155
compiler/table.v
155
compiler/table.v
@ -5,12 +5,14 @@
|
||||
module main
|
||||
|
||||
import math
|
||||
import strings
|
||||
|
||||
struct Table {
|
||||
mut:
|
||||
types []Type
|
||||
consts []Var
|
||||
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
|
||||
packages []string // List of all modules registered by the application
|
||||
imports []string // List of all imports
|
||||
@ -19,6 +21,11 @@ mut:
|
||||
obfuscate bool
|
||||
}
|
||||
|
||||
struct GenTable {
|
||||
fn_name string
|
||||
types []string
|
||||
}
|
||||
|
||||
// Holds import information scoped to the parsed file
|
||||
struct FileImportTable {
|
||||
mut:
|
||||
@ -28,11 +35,11 @@ mut:
|
||||
}
|
||||
|
||||
enum AccessMod {
|
||||
private // private imkey_mut
|
||||
private_mut // private key_mut
|
||||
public // public immkey_mut (readonly)
|
||||
public_mut // public, but key_mut 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)
|
||||
private // private immutable
|
||||
private_mut // private mutable
|
||||
public // public immutable (readonly)
|
||||
public_mut // public, but mutable only in this module
|
||||
public_mut_mut // public and mutable both inside and outside (not recommended to use, that's why it's so verbose)
|
||||
}
|
||||
|
||||
struct Type {
|
||||
@ -47,6 +54,7 @@ mut:
|
||||
is_interface bool
|
||||
is_enum bool
|
||||
enum_vals []string
|
||||
gen_types []string
|
||||
// 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.
|
||||
// This information is needed in the first pass.
|
||||
@ -98,6 +106,14 @@ fn (f Fn) str() string {
|
||||
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) {
|
||||
// }
|
||||
const (
|
||||
@ -121,6 +137,8 @@ fn new_table(obfuscate bool) *Table {
|
||||
mut t := &Table {
|
||||
obf_ids: map[string]int{}
|
||||
fns: map[string]Fn{}
|
||||
//generic_fns: map[string]GenTable{}
|
||||
generic_fns: []GenTable
|
||||
obfuscate: obfuscate
|
||||
}
|
||||
t.register_type('int')
|
||||
@ -142,6 +160,7 @@ fn new_table(obfuscate bool) *Table {
|
||||
t.register_type('bool')
|
||||
t.register_type('void')
|
||||
t.register_type('voidptr')
|
||||
t.register_type('T')
|
||||
t.register_type('va_list')
|
||||
t.register_const('stdin', '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
|
||||
}
|
||||
|
||||
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 {
|
||||
return pkg in table.packages
|
||||
}
|
||||
@ -679,6 +758,72 @@ fn is_valid_int_const(val, typ string) bool {
|
||||
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
|
||||
// this is not optimal
|
||||
fn (table &Table) qualify_module(mod string, file_path string) string {
|
||||
|
54
compiler/vfmt.v
Normal file
54
compiler/vfmt.v
Normal 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--
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ struct Option {
|
||||
// `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }`
|
||||
fn opt_ok(data voidptr, size int) Option {
|
||||
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 {
|
||||
ok: true
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
fn escape(s string) string {
|
||||
pub fn escape(s string) string {
|
||||
return string(byteptr(C.curl_escape(s.str, s.len)))
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,10 @@ pub fn socket(family int, _type int, proto int) ?Socket {
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
pub fn (s Socket) listen() ?int {
|
||||
backlog := 128
|
||||
res := C.listen(s.sockfd, backlog)
|
||||
res := int(C.listen(s.sockfd, backlog))
|
||||
if res < 0 {
|
||||
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
|
||||
@ -139,6 +144,7 @@ pub fn (s Socket) listen_backlog(backlog int) ?int {
|
||||
|
||||
// helper method to create, bind, and listen given port number
|
||||
pub fn listen(port int) ?Socket {
|
||||
println('net.listen($port)')
|
||||
s := socket(AF_INET, SOCK_STREAM, 0) or {
|
||||
return error(err)
|
||||
}
|
||||
@ -153,6 +159,7 @@ pub fn listen(port int) ?Socket {
|
||||
|
||||
// accept first connection request from socket queue
|
||||
pub fn (s Socket) accept() ?Socket {
|
||||
println('accept()')
|
||||
addr := C.sockaddr_storage{}
|
||||
size := 128 // sizeof(sockaddr_storage)
|
||||
sockfd := C.accept(s.sockfd, &addr, &size)
|
||||
@ -251,3 +258,47 @@ pub fn (s Socket) close() ?int {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
108
vlib/time/time.v
108
vlib/time/time.v
@ -56,10 +56,110 @@ pub fn random() Time {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unix(u int) Time {
|
||||
mut t := &C.tm{!}
|
||||
t = C.localtime(&u)
|
||||
return convert_ctime(t)
|
||||
const (
|
||||
// The unsigned zero year for internal calculations.
|
||||
// Must be 1 mod 400, and times before it will not compute correctly,
|
||||
// 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 {
|
||||
|
@ -38,3 +38,16 @@ fn test_days_in_month() {
|
||||
assert check(11, 2001, 30) // November
|
||||
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
69
vlib/vweb/tmpl/tmpl.v
Normal 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
121
vlib/vweb/vweb.v
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user