generics, vweb, comptime codegen, etc

pull/1369/head
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
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 100644
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
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,7 +289,27 @@ fn (p mut Parser) fn_decl() {
if p.pref.obfuscate {
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 {
p.genln('$typ $fn_name_cgen($str_args);')
@ -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,10 +352,24 @@ 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
}
//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 p.tok == .lsbr {
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,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)
// if p.builtin_pkg || p.mod == 'os' ||p.mod=='http'{
if p.mod != 'main' {
p.genln('}')
if !is_generic {
p.genln('}')
}
return
}
p.check_unused_variables()
p.cur_fn = EmptyFn
p.genln('}')
if !is_generic {
p.genln('}')
}
}
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) {
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)
}

View File

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

View File

@ -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) ')
}

View File

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

View File

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

View File

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

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)))
}
fn escape(s string) string {
pub fn escape(s string) string {
return string(byteptr(C.curl_escape(s.str, s.len)))
}

View File

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

View File

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

View File

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

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 100644
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)
}
}
}