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

compiler: make compiler an ordinary vlib/compiler module

* Move compiler/ under vlib/compiler/ .

* Add a minimal compiler/main.v driver program.

* Cleanup compiler/main.v .

* Make most compiler tests pass again.

* Apply the fix by @joe-conigliaro , so that the rest of the compiler tests are fixed too.

* Thanks to @avitkauskas, now the vlib/vcompiler/tests/str_gen_test.v test does not need to be special cased anymore.

* Reapply @joe-conigliaro fix for vgen.
This commit is contained in:
Delyan Angelov
2019-10-13 16:37:43 +03:00
committed by Alexander Medvednikov
parent 59d4535f84
commit 53c64abdeb
71 changed files with 1095 additions and 1080 deletions

421
vlib/compiler/cc.v Normal file
View File

@ -0,0 +1,421 @@
// 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 compiler
import (
os
time
)
fn (v mut V) cc() {
v.build_thirdparty_obj_files()
vexe := os.executable()
// Just create a C/JavaScript file and exit
// for example: `v -o v.c compiler`
if v.out_name.ends_with('.c') || v.out_name.ends_with('.js') {
// Translating V code to JS by launching vjs
$if !js {
if v.out_name.ends_with('.js') {
vjs_path := vexe + 'js'
dir := os.dir(vexe)
if !os.file_exists(vjs_path) {
println('V.js compiler not found, building...')
ret := os.system('$vexe -o $vjs_path -os js $dir/compiler')
if ret == 0 {
println('Done.')
} else {
println('Failed.')
exit(1)
}
}
ret := os.system('$vjs_path -o $v.out_name $v.dir')
if ret == 0 {
println('Done. Run it with `node $v.out_name`')
println('JS backend is at a very early stage.')
}
}
}
os.mv(v.out_name_c, v.out_name)
exit(0)
}
// Cross compiling for Windows
if v.os == .windows {
$if !windows {
v.cc_windows_cross()
return
}
}
$if windows {
if v.os == .msvc {
v.cc_msvc()
return
}
}
// TCC on Linux by default, unless -cc was provided
// TODO if -cc = cc, TCC is still used, default compiler should be
// used instead.
//vdir := os.dir(vexe)
$if linux {
//tcc_path := '$vdir/thirdparty/tcc/bin/tcc'
tcc_path := '/var/tmp/tcc/bin/tcc'
if v.pref.ccompiler == 'cc' && os.file_exists(tcc_path) {
// TODO tcc bug, needs an empty libtcc1.a fila
//os.mkdir('/var/tmp/tcc/lib/tcc/')
//os.create('/var/tmp/tcc/lib/tcc/libtcc1.a')
v.pref.ccompiler = tcc_path
}
}
//linux_host := os.user_os() == 'linux'
v.log('cc() isprod=$v.pref.is_prod outname=$v.out_name')
mut a := [v.pref.cflags, '-std=gnu11', '-w'] // arguments for the C compiler
if v.pref.is_so {
a << '-shared -fPIC '// -Wl,-z,defs'
v.out_name = v.out_name + '.so'
}
if v.pref.build_mode == .build_module {
// Create the modules & out directory if it's not there.
mut out_dir := if v.dir.starts_with('vlib') {
'$v_modules_path${os.path_separator}cache${os.path_separator}$v.dir'
} else {
'$v_modules_path${os.path_separator}$v.dir'
}
pdir := out_dir.all_before_last(os.path_separator)
if !os.dir_exists(pdir) {
os.mkdir_all(pdir)
}
v.out_name = '${out_dir}.o' //v.out_name
println('Building ${v.out_name}...')
}
debug_mode := v.pref.is_debug
mut debug_options := '-g'
mut optimization_options := '-O2'
if v.pref.ccompiler.contains('clang') {
if debug_mode {
debug_options = '-g -O0'
}
optimization_options = '-O3 -flto'
}
if v.pref.ccompiler.contains('gcc') {
if debug_mode {
debug_options = '-g3'
}
optimization_options = '-O3 -fno-strict-aliasing -flto'
}
if debug_mode {
a << debug_options
}
if v.pref.is_prod {
a << optimization_options
}
if debug_mode && os.user_os() != 'windows'{
a << ' -rdynamic ' // needed for nicer symbolic backtraces
}
if v.os != .msvc && v.os != .freebsd {
a << '-Werror=implicit-function-declaration'
}
for f in v.generate_hotcode_reloading_compiler_flags() {
a << f
}
mut libs := ''// builtin.o os.o http.o etc
if v.pref.build_mode == .build_module {
a << '-c'
}
else if v.pref.is_cache {
builtin_o_path := '$v_modules_path${os.path_separator}cache${os.path_separator}vlib${os.path_separator}builtin.o'
if os.file_exists(builtin_o_path) {
libs = builtin_o_path
} else {
println('$builtin_o_path not found... building module builtin')
os.system('$vexe build module vlib${os.path_separator}builtin')
}
for imp in v.table.imports {
if imp.contains('vweb') { continue } // not working
if imp == 'webview' { continue }
imp_path := imp.replace('.', os.path_separator)
path := '$v_modules_path${os.path_separator}cache${os.path_separator}vlib${os.path_separator}${imp_path}.o'
//println('adding ${imp_path}.o')
if os.file_exists(path) {
libs += ' ' + path
} else {
println('$path not found... building module $imp')
os.system('$vexe build module vlib${os.path_separator}$imp_path')
}
}
}
if v.pref.sanitize {
a << '-fsanitize=leak'
}
// Cross compiling linux TODO
/*
sysroot := '/tmp/lld/linuxroot/'
if v.os == .linux && !linux_host {
// Build file.o
a << '-c --sysroot=$sysroot -target x86_64-linux-gnu'
// Right now `out_name` can be `file`, not `file.o`
if !v.out_name.ends_with('.o') {
v.out_name = v.out_name + '.o'
}
}
*/
// Cross compiling windows
//
// Output executable name
a << '-o "$v.out_name"'
if os.dir_exists(v.out_name) {
verror('\'$v.out_name\' is a directory')
}
// macOS code can include objective C TODO remove once objective C is replaced with C
if v.os == .mac {
a << '-x objective-c'
}
// The C file we are compiling
a << '"$v.out_name_c"'
if v.os == .mac {
a << '-x none'
}
// Min macos version is mandatory I think?
if v.os == .mac {
a << '-mmacosx-version-min=10.7'
}
cflags := v.get_os_cflags()
// add .o files
a << cflags.c_options_only_object_files()
// add all flags (-I -l -L etc) not .o files
a << cflags.c_options_without_object_files()
a << libs
// Without these libs compilation will fail on Linux
// || os.user_os() == 'linux'
if v.pref.build_mode != .build_module && (v.os == .linux || v.os == .freebsd || v.os == .openbsd ||
v.os == .netbsd || v.os == .dragonfly || v.os == .solaris) {
a << '-lm -lpthread '
// -ldl is a Linux only thing. BSDs have it in libc.
if v.os == .linux {
a << ' -ldl '
}
}
if v.os == .js && os.user_os() == 'linux' {
a << '-lm'
}
args := a.join(' ')
cmd := '${v.pref.ccompiler} $args'
// Run
if v.pref.show_c_cmd || v.pref.is_verbose {
println('\n==========')
println(cmd)
}
ticks := time.ticks()
res := os.exec(cmd) or { verror(err) return }
if res.exit_code != 0 {
if res.exit_code == 127 {
// the command could not be found by the system
verror('C compiler error, while attempting to run: \n' +
'-----------------------------------------------------------\n' +
'$cmd\n' +
'-----------------------------------------------------------\n' +
'Probably your C compiler is missing. \n' +
'Please reinstall it, or make it available in your PATH.')
}
if v.pref.is_debug {
println(res.output)
} else {
partial_output := res.output.limit(200).trim_right('\r\n')
print(partial_output)
if res.output.len > partial_output.len {
println('...\n(Use `v -g` to print the entire error message)\n')
}else{
println('')
}
}
verror('C error. This should never happen. ' +
'Please create a GitHub issue: https://github.com/vlang/v/issues/new/choose')
}
diff := time.ticks() - ticks
// Print the C command
if v.pref.show_c_cmd || v.pref.is_verbose {
println('${v.pref.ccompiler} took $diff ms')
println('=========\n')
}
// Link it if we are cross compiling and need an executable
/*
if v.os == .linux && !linux_host && v.pref.build_mode != .build {
v.out_name = v.out_name.replace('.o', '')
obj_file := v.out_name + '.o'
println('linux obj_file=$obj_file out_name=$v.out_name')
ress := os.exec('/usr/local/Cellar/llvm/8.0.0/bin/ld.lld --sysroot=$sysroot ' +
'-v -o $v.out_name ' +
'-m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 ' +
'/usr/lib/x86_64-linux-gnu/crt1.o ' +
'$sysroot/lib/x86_64-linux-gnu/libm-2.28.a ' +
'/usr/lib/x86_64-linux-gnu/crti.o ' +
obj_file +
' /usr/lib/x86_64-linux-gnu/libc.so ' +
'/usr/lib/x86_64-linux-gnu/crtn.o') or {
verror(err)
return
}
println(ress.output)
println('linux cross compilation done. resulting binary: "$v.out_name"')
}
*/
if !v.pref.is_keep_c && v.out_name_c != 'v.c' {
os.rm(v.out_name_c)
}
if v.pref.compress {
$if windows {
println('-compress does not work on Windows for now')
return
}
ret := os.system('strip $v.out_name')
if ret != 0 {
println('strip failed')
return
}
ret2 := os.system('upx --lzma -qqq $v.out_name')
if ret2 != 0 {
println('upx failed')
$if mac {
println('install upx with `brew install upx`')
}
$if linux {
println('install upx\n' +
'for example, on Debian/Ubuntu run `sudo apt install upx`')
}
$if windows {
// :)
}
}
}
}
fn (c mut V) cc_windows_cross() {
if !c.out_name.ends_with('.exe') {
c.out_name = c.out_name + '.exe'
}
mut args := '-o $c.out_name -w -L. '
cflags := c.get_os_cflags()
// -I flags
args += cflags.c_options_before_target()
mut libs := ''
if c.pref.build_mode == .default_mode {
libs = '"$v_modules_path/vlib/builtin.o"'
if !os.file_exists(libs) {
println('`$libs` not found')
exit(1)
}
for imp in c.table.imports {
libs += ' "$v_modules_path/vlib/${imp}.o"'
}
}
args += ' $c.out_name_c '
args += cflags.c_options_after_target()
println('Cross compiling for Windows...')
winroot := '$v_modules_path/winroot'
if !os.dir_exists(winroot) {
winroot_url := 'https://github.com/vlang/v/releases/download/v0.1.10/winroot.zip'
println('"$winroot" not found.')
println('Download it from $winroot_url and save it in $v_modules_path')
println('Unzip it afterwards.\n')
println('winroot.zip contains all library and header files needed '+
'to cross-compile for Windows.')
exit(1)
}
mut obj_name := c.out_name
obj_name = obj_name.replace('.exe', '')
obj_name = obj_name.replace('.o.o', '.o')
include := '-I $winroot/include '
cmd := 'clang -o $obj_name -w $include -m32 -c -target x86_64-win32 $v_modules_path/$c.out_name_c'
if c.pref.show_c_cmd {
println(cmd)
}
if os.system(cmd) != 0 {
println('Cross compilation for Windows failed. Make sure you have clang installed.')
exit(1)
}
if c.pref.build_mode != .build_module {
link_cmd := 'lld-link $obj_name $winroot/lib/libcmt.lib ' +
'$winroot/lib/libucrt.lib $winroot/lib/kernel32.lib $winroot/lib/libvcruntime.lib ' +
'$winroot/lib/uuid.lib'
if c.pref.show_c_cmd {
println(link_cmd)
}
if os.system(link_cmd) != 0 {
println('Cross compilation for Windows failed. Make sure you have lld linker installed.')
exit(1)
}
// os.rm(obj_name)
}
println('Done!')
}
fn (c &V) build_thirdparty_obj_files() {
for flag in c.get_os_cflags() {
if flag.value.ends_with('.o') {
rest_of_module_flags := c.get_rest_of_module_cflags( flag )
if c.os == .msvc {
build_thirdparty_obj_file_with_msvc(flag.value, rest_of_module_flags)
}
else {
build_thirdparty_obj_file(flag.value, rest_of_module_flags)
}
}
}
}
fn find_c_compiler() string {
args := env_vflags_and_os_args().join(' ')
defaultcc := find_c_compiler_default()
return get_arg( args, 'cc', defaultcc )
}
fn find_c_compiler_default() string {
//fast_clang := '/usr/local/Cellar/llvm/8.0.0/bin/clang'
//if os.file_exists(fast_clang) {
// return fast_clang
//}
// TODO fix $if after 'string'
$if windows { return 'gcc' }
return 'cc'
}
fn find_c_compiler_thirdparty_options() string {
fullargs := env_vflags_and_os_args()
mut cflags := get_cmdline_cflags( fullargs )
$if !windows {
cflags += ' -fPIC'
}
if '-m32' in fullargs {
cflags += ' -m32'
}
return cflags
}
fn get_cmdline_cflags(args []string) string {
mut cflags := ''
for ci, cv in args {
if cv == '-cflags' {
cflags += args[ci+1] + ' '
}
}
return cflags
}

190
vlib/compiler/cflags.v Normal file
View File

@ -0,0 +1,190 @@
// 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 compiler
import os
// parsed cflag
struct CFlag{
mod string // the module in which the flag was given
os string // eg. windows | darwin | linux
name string // eg. -I
value string // eg. /path/to/include
}
fn (c &CFlag) str() string {
return 'CFlag{ name: "$c.name" value: "$c.value" mod: "$c.mod" os: "$c.os" }'
}
// get flags for current os
fn (v &V) get_os_cflags() []CFlag {
mut flags := []CFlag
for flag in v.table.cflags {
if flag.os == ''
|| (flag.os == 'linux' && v.os == .linux)
|| (flag.os == 'darwin' && v.os == .mac)
|| (flag.os == 'freebsd' && v.os == .freebsd)
|| (flag.os == 'windows' && (v.os == .windows || v.os == .msvc)) {
flags << flag
}
}
return flags
}
fn (v &V) get_rest_of_module_cflags(c &CFlag) []CFlag {
mut flags := []CFlag
cflags := v.get_os_cflags()
for flag in cflags {
if c.mod == flag.mod {
if c.name == flag.name && c.value == flag.value && c.os == flag.os { continue }
flags << flag
}
}
return flags
}
// format flag
fn (cf &CFlag) format() string {
mut value := cf.value
if cf.name == '-l' && value.len>0 {
return '${cf.name}${value}'.trim_space()
}
// convert to absolute path
if cf.name == '-I' || cf.name == '-L' || value.ends_with('.o') {
value = '"'+os.realpath(value)+'"'
}
return '$cf.name $value'.trim_space()
}
// check if cflag is in table
fn (table &Table) has_cflag(cflag CFlag) bool {
for cf in table.cflags {
if cf.os == cflag.os && cf.name == cflag.name && cf.value == cflag.value {
return true
}
}
return false
}
// parse the flags to (table.cflags) []CFlag
// Note: clean up big time (joe-c)
fn (table mut Table) parse_cflag(cflag string, mod string) {
allowed_flags := [
'framework',
'library',
'I', 'l', 'L',
]
mut flag := cflag.trim_space()
if flag == '' {
return
}
mut fos := ''
mut name := ''
if flag.starts_with('linux') || flag.starts_with('darwin') || flag.starts_with('freebsd') || flag.starts_with('windows') {
pos := flag.index(' ')
fos = flag.left(pos).trim_space()
flag = flag.right(pos).trim_space()
}
for {
mut index := -1
mut value := ''
if flag[0] == `-` {
for f in allowed_flags {
i := 1+f.len
if i < flag.len && f == flag.substr(1,i) {
name = flag.left(i).trim_space()
flag = flag.right(i).trim_space()
break
}
}
}
for i in [flag.index(' '), flag.index(',')] {
if index == -1 || (i != -1 && i < index) {
index = i
}
}
if index != -1 && flag[index] == ` ` && flag[index+1] == `-` {
for f in allowed_flags {
i := index+f.len
if i < flag.len && f == flag.substr(index, i) {
index = i
break
}
}
value = flag.left(index).trim_space()
flag = flag.right(index).trim_space()
}
else if index != -1 && index < flag.len-2 && flag[index] == `,` {
value = flag.left(index).trim_space()
flag = flag.right(index+1).trim_space()
}
else {
value = flag.trim_space()
index = -1
}
cf := CFlag{
mod: mod,
os: fos,
name: name,
value: value
}
if !table.has_cflag(cf) {
table.cflags << cf
}
if index == -1 {
break
}
}
}
//TODO: implement msvc specific c_options_before_target and c_options_after_target ...
fn (cflags []CFlag) c_options_before_target() string {
$if msvc {
return ''
}
// -I flags, optimization flags and so on
mut args:=[]string
for flag in cflags {
if flag.name != '-l' {
args << flag.format()
}
}
return args.join(' ')
}
fn (cflags []CFlag) c_options_after_target() string {
$if msvc {
return ''
}
// -l flags (libs)
mut args:=[]string
for flag in cflags {
if flag.name == '-l' {
args << flag.format()
}
}
return args.join(' ')
}
fn (cflags []CFlag) c_options_without_object_files() string {
mut args:=[]string
for flag in cflags {
if flag.value.ends_with('.o') || flag.value.ends_with('.obj') {
continue
}
args << flag.format()
}
return args.join(' ')
}
fn (cflags []CFlag) c_options_only_object_files() string {
mut args:=[]string
for flag in cflags {
if flag.value.ends_with('.o') || flag.value.ends_with('.obj') {
args << flag.format()
}
}
return args.join(' ')
}

369
vlib/compiler/cgen.v Normal file
View File

@ -0,0 +1,369 @@
// 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 compiler
import os
struct CGen {
out os.File
out_path string
//types []string
thread_fns []string
//buf strings.Builder
is_user bool
mut:
lines []string
typedefs []string
type_aliases []string
includes []string
thread_args []string
consts []string
fns []string
so_fns []string
consts_init []string
pass Pass
nogen bool
tmp_line string
cur_line string
prev_line string
is_tmp bool
fn_main string
stash string
file string
line int
line_directives bool
cut_pos int
}
fn new_cgen(out_name_c string) &CGen {
path := out_name_c
out := os.create(path) or {
println('failed to create $path')
return &CGen{}
}
gen := &CGen {
out_path: path
out: out
//buf: strings.new_builder(10000)
lines: make(0, 1000, sizeof(string))
}
return gen
}
fn (g mut CGen) genln(s string) {
if g.nogen || g.pass != .main {
return
}
if g.is_tmp {
g.tmp_line = '$g.tmp_line $s\n'
return
}
g.cur_line = '$g.cur_line $s'
if g.cur_line != '' {
if g.line_directives && g.cur_line.trim_space() != '' {
if g.file.len > 0 && g.line > 0 {
g.lines << '\n#line $g.line "$g.file"'
}
}
g.lines << g.cur_line
g.prev_line = g.cur_line
g.cur_line = ''
}
}
fn (g mut CGen) gen(s string) {
if g.nogen || g.pass != .main {
return
}
if g.is_tmp {
g.tmp_line = '$g.tmp_line $s'
}
else {
g.cur_line = '$g.cur_line $s'
}
}
fn (g mut CGen) resetln(s string) {
if g.nogen || g.pass != .main {
return
}
if g.is_tmp {
g.tmp_line = s
}
else {
g.cur_line = s
}
}
fn (g mut CGen) save() {
s := g.lines.join('\n')
g.out.writeln(s)
g.out.close()
}
fn (g mut CGen) start_tmp() {
if g.is_tmp {
println(g.tmp_line)
println('start_tmp() already started. cur_line="$g.cur_line"')
exit(1)
}
// kg.tmp_lines_pos++
g.tmp_line = ''
g.is_tmp = true
}
fn (g mut CGen) end_tmp() string {
g.is_tmp = false
res := g.tmp_line
g.tmp_line = ''
return res
}
fn (g &CGen) add_placeholder() int {
if g.is_tmp {
return g.tmp_line.len
}
return g.cur_line.len
}
fn (g mut CGen) start_cut() {
g.cut_pos = g.add_placeholder()
}
fn (g mut CGen) cut() string {
pos := g.cut_pos
g.cut_pos = 0
if g.is_tmp {
res := g.tmp_line.right(pos)
g.tmp_line = g.tmp_line.left(pos)
return res
}
res := g.cur_line.right(pos)
g.cur_line = g.cur_line.left(pos)
return res
}
fn (g mut CGen) set_placeholder(pos int, val string) {
if g.nogen || g.pass != .main {
return
}
// g.lines.set(pos, val)
if g.is_tmp {
left := g.tmp_line.left(pos)
right := g.tmp_line.right(pos)
g.tmp_line = '${left}${val}${right}'
return
}
left := g.cur_line.left(pos)
right := g.cur_line.right(pos)
g.cur_line = '${left}${val}${right}'
// g.genln('')
}
fn (g mut CGen) insert_before(val string) {
if g.nogen {
return
}
prev := g.lines[g.lines.len - 1]
g.lines[g.lines.len - 1] = '$prev \n $val \n'
}
fn (g mut CGen) register_thread_fn(wrapper_name, wrapper_text, struct_text string) {
for arg in g.thread_args {
if arg.contains(wrapper_name) {
return
}
}
g.thread_args << struct_text
g.thread_args << wrapper_text
}
fn (v &V) prof_counters() string {
mut res := []string
// Global fns
//for f in c.table.fns {
//res << 'double ${c.table.cgen_name(f)}_time;'
//}
// Methods
/*
for typ in c.table.types {
// println('')
for f in typ.methods {
// res << f.cgen_name()
res << 'double ${c.table.cgen_name(f)}_time;'
// println(f.cgen_name())
}
}
*/
return res.join(';\n')
}
fn (p &Parser) print_prof_counters() string {
mut res := []string
// Global fns
//for f in p.table.fns {
//counter := '${p.table.cgen_name(f)}_time'
//res << 'if ($counter) printf("%%f : $f.name \\n", $counter);'
//}
// Methods
/*
for typ in p.table.types {
// println('')
for f in typ.methods {
counter := '${p.table.cgen_name(f)}_time'
res << 'if ($counter) printf("%%f : ${p.table.cgen_name(f)} \\n", $counter);'
// res << 'if ($counter) printf("$f.name : %%f\\n", $counter);'
// res << f.cgen_name()
// res << 'double ${f.cgen_name()}_time;'
// println(f.cgen_name())
}
}
*/
return res.join(';\n')
}
fn (p mut Parser) gen_typedef(s string) {
if !p.first_pass() {
return
}
p.cgen.typedefs << s
}
fn (p mut Parser) gen_type_alias(s string) {
if !p.first_pass() {
return
}
p.cgen.type_aliases << s
}
fn (g mut CGen) add_to_main(s string) {
g.fn_main = g.fn_main + s
}
fn build_thirdparty_obj_file(path string, moduleflags []CFlag) {
obj_path := os.realpath(path)
if os.file_exists(obj_path) {
return
}
println('$obj_path not found, building it...')
parent := os.dir(obj_path)
files := os.ls(parent)
mut cfiles := ''
for file in files {
if file.ends_with('.c') {
cfiles += '"' + os.realpath( parent + os.path_separator + file ) + '" '
}
}
cc := find_c_compiler()
cc_thirdparty_options := find_c_compiler_thirdparty_options()
btarget := moduleflags.c_options_before_target()
atarget := moduleflags.c_options_after_target()
cmd := '$cc $cc_thirdparty_options $btarget -c -o "$obj_path" $cfiles $atarget '
res := os.exec(cmd) or {
println('failed thirdparty object build cmd: $cmd')
verror(err)
return
}
println(res.output)
}
fn os_name_to_ifdef(name string) string {
switch name {
case 'windows': return '_WIN32'
case 'mac': return '__APPLE__'
case 'linux': return '__linux__'
case 'freebsd': return '__FreeBSD__'
case 'openbsd': return '__OpenBSD__'
case 'netbsd': return '__NetBSD__'
case 'dragonfly': return '__DragonFly__'
case 'msvc': return '_MSC_VER'
case 'android': return '__BIONIC__'
case 'js': return '_VJS'
case 'solaris': return '__sun'
}
verror('bad os ifdef name "$name"')
return ''
}
fn platform_postfix_to_ifdefguard(name string) string {
switch name {
case '.v': return '' // no guard needed
case '_win.v': return '#ifdef _WIN32'
case '_nix.v': return '#ifndef _WIN32'
case '_lin.v': return '#ifdef __linux__'
case '_mac.v': return '#ifdef __APPLE__'
case '_solaris.v': return '#ifdef __sun'
}
verror('bad platform_postfix "$name"')
return ''
}
// C struct definitions, ordered
// Sort the types, make sure types that are referenced by other types
// are added before them.
fn (v &V) type_definitions() string {
mut types := []Type // structs that need to be sorted
mut builtin_types := []Type // builtin types
// builtin types need to be on top
builtins := ['string', 'array', 'map', 'Option']
for builtin in builtins {
typ := v.table.typesmap[builtin]
builtin_types << typ
}
// everything except builtin will get sorted
for t_name, t in v.table.typesmap {
if t_name in builtins {
continue
}
types << t
}
// sort structs
types_sorted := sort_structs(types)
// Generate C code
res := types_to_c(builtin_types,v.table) + '\n//----\n' +
types_to_c(types_sorted, v.table)
return res
}
// sort structs by dependant fields
fn sort_structs(types []Type) []Type {
mut dep_graph := new_dep_graph()
// types name list
mut type_names := []string
for t in types {
type_names << t.name
}
// loop over types
for t in types {
// create list of deps
mut field_deps := []string
for field in t.fields {
// skip if not in types list or already in deps
if !(field.typ in type_names) || field.typ in field_deps {
continue
}
field_deps << field.typ
}
// add type and dependant types to graph
dep_graph.add(t.name, field_deps)
}
// sort graph
dep_graph_sorted := dep_graph.resolve()
if !dep_graph_sorted.acyclic {
verror('error: cgen.sort_structs() DGNAC.\nplease create a new issue here: https://github.com/vlang/v/issues and tag @joe-conigliaro')
}
// sort types
mut types_sorted := []Type
for node in dep_graph_sorted.nodes {
for t in types {
if t.name == node.name {
types_sorted << t
continue
}
}
}
return types_sorted
}

172
vlib/compiler/cheaders.v Normal file
View File

@ -0,0 +1,172 @@
module compiler
const (
CommonCHeaders = '
#include <stdio.h> // TODO remove all these includes, define all function signatures and types manually
#include <stdlib.h>
#include <signal.h>
#include <stdarg.h> // for va_list
#include <inttypes.h> // int64_t etc
#include <string.h> // memcpy
#ifndef _WIN32
#include <ctype.h>
#include <locale.h> // tolower
#include <sys/time.h>
#include <unistd.h> // sleep
#endif
#ifdef __APPLE__
#include <libproc.h> // proc_pidpath
#include <execinfo.h> // backtrace and backtrace_symbols_fd
#endif
#ifdef __linux__
#ifndef __BIONIC__
#include <execinfo.h> // backtrace and backtrace_symbols_fd
#endif
#pragma weak backtrace
#pragma weak backtrace_symbols_fd
#endif
#ifdef __linux__
#include <sys/types.h>
#include <sys/wait.h> // os__wait uses wait on nix
#endif
#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/wait.h> // os__wait uses wait on nix
#endif
#ifdef __DragonFly__
#include <sys/types.h>
#include <sys/wait.h> // os__wait uses wait on nix
#endif
#define EMPTY_STRUCT_DECLARATION
#define EMPTY_STRUCT_INITIALIZATION 0
// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is...
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[])
#define TCCSKIP(x) x
#ifdef __TINYC__
#undef EMPTY_STRUCT_INITIALIZATION
#define EMPTY_STRUCT_INITIALIZATION
#undef EMPTY_ARRAY_OF_ELEMS
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n])
#undef TCCSKIP
#define TCCSKIP(x)
#endif
#define OPTION_CAST(x) (x)
#ifdef _WIN32
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define WIN32_LEAN_AND_MEAN
#define _UNICODE
#define UNICODE
#include <windows.h>
// must be included after <windows.h>
#ifndef __TINYC__
#include <shellapi.h>
#endif
#include <io.h> // _waccess
#include <fcntl.h> // _O_U8TEXT
#include <direct.h> // _wgetcwd
//#include <WinSock2.h>
#ifdef _MSC_VER
// On MSVC these are the same (as long as /volatile:ms is passed)
#define _Atomic volatile
// MSVC cannot parse some things properly
#undef EMPTY_STRUCT_DECLARATION
#undef OPTION_CAST
#define EMPTY_STRUCT_DECLARATION int ____dummy_variable
#define OPTION_CAST(x)
#endif
#else
#include <pthread.h>
#endif
//================================== TYPEDEFS ================================*/
typedef int64_t i64;
typedef int16_t i16;
typedef int8_t i8;
typedef uint64_t u64;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t byte;
typedef uint32_t rune;
typedef float f32;
typedef double f64;
typedef unsigned char* byteptr;
typedef int* intptr;
typedef void* voidptr;
typedef struct array array;
typedef struct map map;
typedef array array_string;
typedef array array_int;
typedef array array_byte;
typedef array array_f32;
typedef array array_f64;
typedef map map_int;
typedef map map_string;
#ifndef bool
typedef int bool;
#define true 1
#define false 0
#endif
//============================== HELPER C MACROS =============================*/
#define _PUSH(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push(arr, &tmp);}
#define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many(arr, tmp.data, tmp.len);}
#define _IN(typ, val, arr) array_##typ##_contains(arr, val)
#define _IN_MAP(val, m) map_exists(m, val)
#define DEFAULT_EQUAL(a, b) (a == b)
#define DEFAULT_NOT_EQUAL(a, b) (a != b)
#define DEFAULT_LT(a, b) (a < b)
#define DEFAULT_LE(a, b) (a <= b)
#define DEFAULT_GT(a, b) (a > b)
#define DEFAULT_GE(a, b) (a >= b)
//================================== GLOBALS =================================*/
byteptr g_str_buf;
int load_so(byteptr);
void reload_so();
'
js_headers = '
var array_string = function() {}
var array_byte = function() {}
var array_int = function() {}
var byte = function() {}
var double = function() {}
var int = function() {}
var f64 = function() {}
var f32 = function() {}
var i64 = function() {}
var i32 = function() {}
var i16 = function() {}
var u64 = function() {}
var u32 = function() {}
var u16 = function() {}
var i8 = function() {}
var u8 = function() {}
var bool = function() {}
var rune = function() {}
var map_string = function() {}
var map_int = function() {}
'
)

View File

@ -0,0 +1,270 @@
module compiler
import (
os
term
)
//////////////////////////////////////////////////////////////////////////////////////////////////
// NB: The code in this file is organized in layers (between the ///// lines).
// This allows for easier keeping in sync of error/warn functions.
// The functions in each of the layers, call the functions from the layers *below*.
// The functions in each of the layers, also have more details about the warn/error situation,
// so they can display more informative message, so please call the lowest level variant you can.
//////////////////////////////////////////////////////////////////////////////////////////////////
// TLDR: If you have a token index, call:
// p.error_with_token_index(msg, token_index)
// ... not just :
// p.error(msg)
//////////////////////////////////////////////////////////////////////////////////////////////////
fn (p mut Parser) error(s string) {
// no positioning info, so just assume that the last token was the culprit:
p.error_with_token_index(s, p.token_idx-1 )
}
fn (p mut Parser) warn(s string) {
p.warn_with_token_index(s, p.token_idx-1 )
}
//////////////////////////////////////////////////////////////////////////////////////////////////
fn (p mut Parser) production_error_with_token_index(e string, tokenindex int) {
if p.pref.is_prod {
p.error_with_token_index( e, tokenindex )
}else {
p.warn_with_token_index( e, tokenindex )
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
fn (p mut Parser) error_with_token_index(s string, tokenindex int) {
p.error_with_position(s, p.scanner.get_scanner_pos_of_token( p.tokens[ tokenindex ] ) )
}
fn (p mut Parser) warn_with_token_index(s string, tokenindex int) {
p.warn_with_position(s, p.scanner.get_scanner_pos_of_token( p.tokens[ tokenindex ] ) )
}
//////////////////////////////////////////////////////////////////////////////////////////////////
fn (p mut Parser) error_with_position(s string, sp ScannerPos) {
p.print_error_context()
e := normalized_error( s )
p.scanner.goto_scanner_position( sp )
p.scanner.error_with_col(e, sp.pos - sp.last_nl_pos)
}
fn (p mut Parser) warn_with_position(s string, sp ScannerPos) {
// on a warning, restore the scanner state after printing the warning:
cpos := p.scanner.get_scanner_pos()
e := normalized_error( s )
p.scanner.goto_scanner_position( sp )
p.scanner.warn_with_col(e, sp.pos - sp.last_nl_pos)
p.scanner.goto_scanner_position( cpos )
}
//////////////////////////////////////////////////////////////////////////////////////////////////
fn (s &Scanner) error(msg string) {
s.error_with_col(msg, 0)
}
fn (s &Scanner) warn(msg string) {
s.warn_with_col(msg, 0)
}
//////////////////////////////////////////////////////////////////////////////////////////////////
fn (s &Scanner) warn_with_col(msg string, col int) {
fullpath := s.get_error_filepath()
color_on := s.is_color_output_on()
final_message := if color_on { term.bold(term.bright_blue( msg )) } else { msg }
eprintln('warning: ${fullpath}:${s.line_nr+1}:${col}: $final_message')
}
fn (s &Scanner) error_with_col(msg string, col int) {
fullpath := s.get_error_filepath()
color_on := s.is_color_output_on()
final_message := if color_on { term.red( term.bold( msg ) ) } else { msg }
// The filepath:line:col: format is the default C compiler
// error output format. It allows editors and IDE's like
// emacs to quickly find the errors in the output
// and jump to their source with a keyboard shortcut.
// NB: using only the filename may lead to inability of IDE/editors
// to find the source file, when the IDE has a different working folder than v itself.
eprintln('${fullpath}:${s.line_nr + 1}:${col}: $final_message')
if s.should_print_line_on_error && s.file_lines.len > 0 {
context_start_line := imax(0, (s.line_nr - error_context_before + 1 ))
context_end_line := imin(s.file_lines.len, (s.line_nr + error_context_after + 1 ))
for cline := context_start_line; cline < context_end_line; cline++ {
line := '${(cline+1):5d}| ' + s.file_lines[ cline ]
coloredline := if cline == s.line_nr && color_on { term.red(line) } else { line }
eprintln( coloredline )
if cline != s.line_nr { continue }
// The pointerline should have the same spaces/tabs as the offending
// line, so that it prints the ^ character exactly on the *same spot*
// where it is needed. That is the reason we can not just
// use strings.repeat(` `, col) to form it.
mut pointerline := []string
for i , c in line {
if i < col {
x := if c.is_space() { c } else { ` ` }
pointerline << x.str()
continue
}
pointerline << if color_on { term.bold( term.blue('^') ) } else { '^' }
break
}
eprintln( ' ' + pointerline.join('') )
}
}
exit(1)
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Misc error helper functions, can be called by any of the functions above
[inline] fn (p &Parser) cur_tok_index() int { return p.token_idx - 1 }
[inline] fn imax(a,b int) int { return if a > b { a } else { b } }
[inline] fn imin(a,b int) int { return if a < b { a } else { b } }
fn (s &Scanner) get_error_filepath() string {
if s.should_print_relative_paths_on_error {
return s.file_path
}
return os.realpath( s.file_path )
}
fn (s &Scanner) is_color_output_on() bool {
return s.should_print_errors_in_color && term.can_show_color_on_stderr()
}
fn (p mut Parser) print_error_context(){
// Dump all vars and types for debugging
if p.pref.is_debug {
// os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types))
os.write_file('fns.txt', p.table.debug_fns())
}
if p.pref.is_verbose || p.pref.is_debug {
println('pass=$p.pass fn=`$p.cur_fn.name`\n')
}
p.cgen.save()
// V up hint
cur_path := os.getwd()
if !p.pref.is_repl && !p.pref.is_test && ( p.file_path_id.contains('v/compiler') || cur_path.contains('v/compiler') ){
println('\n=========================')
println('It looks like you are building V. It is being frequently updated every day.')
println('If you didn\'t modify V\'s code, most likely there was a change that ')
println('lead to this error.')
println('\nRun `v up`, that will most likely fix it.')
//println('\nIf this doesn\'t help, re-install V from source or download a precompiled' + ' binary from\nhttps://vlang.io.')
println('\nIf this doesn\'t help, please create a GitHub issue.')
println('=========================\n')
}
if p.pref.is_debug {
print_backtrace()
}
// p.scanner.debug_tokens()
}
fn normalized_error( s string ) string {
// Print `[]int` instead of `array_int` in errors
return s.replace('array_', '[]')
.replace('__', '.')
.replace('Option_', '?')
.replace('main.', '')
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// The goal of ScannerPos is to track the current scanning position,
// so that if there is an error found later, v could show a more accurate
// position about where the error initially was.
// NB: The fields of ScannerPos *should be kept synchronized* with the
// corresponding fields in Scanner.
struct ScannerPos {
mut:
pos int
line_nr int
last_nl_pos int
}
fn (s ScannerPos) str() string {
return 'ScannerPos{ ${s.pos:5d} , ${s.line_nr:5d} , ${s.last_nl_pos:5d} }'
}
fn (s &Scanner) get_scanner_pos() ScannerPos {
return ScannerPos{ pos: s.pos line_nr: s.line_nr last_nl_pos: s.last_nl_pos }
}
fn (s mut Scanner) goto_scanner_position(scp ScannerPos) {
s.pos = scp.pos
s.line_nr = scp.line_nr
s.last_nl_pos = scp.last_nl_pos
}
// get_scanner_pos_of_token rescans *the whole source* till it reaches {t.line_nr, t.col} .
fn (s mut Scanner) get_scanner_pos_of_token(t &Token) ScannerPos {
// This rescanning is done just once on error, so it is fine for now.
// Be careful for the performance implications, if you want to
// do it more frequently. The alternative would be to store
// the scanpos (12 bytes) for each token, and there are potentially many tokens.
tline := t.line_nr
tcol := if t.line_nr == 0 { t.col + 1 } else { t.col - 1 }
// save the current scanner position, it will be restored later
cpos := s.get_scanner_pos()
mut sptoken := ScannerPos{}
// Starting from the start, scan the source lines
// till the desired tline is reached, then
// s.pos + tcol would be the proper position
// of the token. Continue scanning for some more lines of context too.
s.goto_scanner_position(ScannerPos{})
s.file_lines = []string
mut prevlinepos := 0
// NB: TCC BUG workaround: removing the `mut ate:=0 ate++` line
// below causes a bug in v, when v is compiled with tcc, and v
// wants to report the error: 'the following imports were never used:'
//
// This can be reproduced, if you follow the steps:
// a) ./v -cc tcc -o v compiler ;
// b) ./v vlib/builtin/hashmap_test.v'
//
// In this case, prevlinepos gets a random value on each run.
// Any kind of operation may be used seemingly, as long as
// there is a new stack allocation that will 'protect' prevlinepos.
//////////////////////////////////////////////////////////////////
mut ate:=0 ate++ // This var will be smashed by TCC, instead of
/////////////////// prevlinepos. The cause is the call to
/////////////////// s.get_scanner_pos()
/////////////////// which just returns a struct, and that works
/////////////////// in gcc and clang, but causes the TCC problem.
for {
prevlinepos = s.pos
if s.pos >= s.text.len { break }
if s.line_nr > tline + 10 { break }
////////////////////////////////////////
if tline == s.line_nr {
sptoken = s.get_scanner_pos()
sptoken.pos += tcol
}
s.ignore_line() s.eat_single_newline()
sline := s.text.substr( prevlinepos, s.pos )//.trim_right('\r\n')
s.file_lines << sline
}
//////////////////////////////////////////////////
s.goto_scanner_position(cpos)
return sptoken
}
fn (s mut Scanner) eat_single_newline(){
if s.pos >= s.text.len { return }
if s.expect('\r\n', s.pos) { s.pos += 2 return }
if s.text[ s.pos ] == `\n` { s.pos ++ return }
if s.text[ s.pos ] == `\r` { s.pos ++ return }
}

319
vlib/compiler/comptime.v Normal file
View File

@ -0,0 +1,319 @@
// 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 compiler
import (
vweb.tmpl // for `$vweb_html()`
os
strings
)
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 supported_platforms {
ifdef_name := os_name_to_ifdef(name)
if not {
p.genln('#ifndef $ifdef_name')
}
else {
p.genln('#ifdef $ifdef_name')
}
p.check(.lcbr)
os := os_from_string(name)
if false && p.fileis('runtime.v') && os != p.os {
// `$if os {` for a different target, skip everything inside
// to avoid compilation errors (like including <windows.h>
// on non-Windows systems)
mut stack := 1
for {
if p.tok == .lcbr {
stack++
} else if p.tok == .rcbr {
stack--
}
if p.tok == .eof {
break
}
if stack <= 0 && p.tok == .rcbr {
//p.warn('exiting $stack')
p.next()
break
}
p.next()
}
} else {
p.statements_no_rcbr()
}
if ! (p.tok == .dollar && p.peek() == .key_else) {
p.genln('#endif')
}
}
else if name == 'debug' {
p.genln('#ifdef VDEBUG')
p.check(.lcbr)
p.statements_no_rcbr()
p.genln('#endif')
}
else if name == 'tinyc' {
p.genln('#ifdef __TINYC__')
p.check(.lcbr)
p.statements_no_rcbr()
p.genln('#endif')
}
else {
println('Supported platforms:')
println(supported_platforms)
p.error('unknown platform `$name`')
}
if_returns := p.returns
p.returns = false
//p.gen('/* returns $p.returns */')
if p.tok == .dollar && p.peek() == .key_else {
p.next()
p.next()
p.check(.lcbr)
p.genln('#else')
p.statements_no_rcbr()
p.genln('#endif')
else_returns := p.returns
p.returns = if_returns && else_returns
//p.gen('/* returns $p.returns */')
}
}
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)
// }
}
// $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' {
mut path := p.cur_fn.name + '.html'
if p.pref.is_debug {
println('compiling tmpl $path')
}
if !os.file_exists(path) {
// Can't find the template file in current directory,
// try looking next to the vweb program, in case it's run with
// v path/to/vweb_app.v
path = os.dir(p.scanner.file_path) + '/' + path
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)
if os.file_exists('.vwebtmpl.v') {
os.rm('.vwebtmpl.v')
}
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_from_file('.vwebtmpl.v')
if !p.pref.is_debug {
os.rm('.vwebtmpl.v')
}
pp.is_vweb = true
pp.set_current_fn( p.cur_fn ) // give access too all variables in current function
pp.parse(.main)
pp.v.add_parser(pp)
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 hash="$hash"')
p.next()
if hash.starts_with('flag ') {
mut flag := hash.right(5)
// expand `@VROOT` `@VMOD` to absolute path
flag = flag.replace('@VROOT', p.vroot)
flag = flag.replace('@VMOD', v_modules_path)
//p.log('adding flag "$flag"')
p.table.parse_cflag(flag, p.mod)
return
}
if hash.starts_with('include') {
if p.first_pass() && !p.is_vh {
if p.file_pcguard.len != 0 {
//println('p: $p.file_platform $p.file_pcguard')
p.cgen.includes << '$p.file_pcguard\n#$hash\n#endif'
return
}
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
}
// Don't parse a non-JS V file (`#-js` flag)
else if hash == '-js' {
$if js {
for p.tok != .eof {
p.next()
}
} $else {
p.next()
}
}
else {
$if !js {
if !p.can_chash {
println('hash="$hash"')
println(hash.starts_with('include'))
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 i, method in typ.methods {
if method.typ != 'void' {
continue
}
receiver := method.args[0]
amp := if receiver.is_mut { '&' } else { '' }
if i > 0 {
p.gen(' 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)
if p.tok == .key_orelse {
p.check(.key_orelse)
p.genln('else {')
p.check(.lcbr)
p.statements()
}
}
fn (p mut Parser) gen_array_str(typ Type) {
p.add_method(typ.name, Fn{
name: 'str'
typ: 'string'
args: [Var{typ: typ.name, is_arg:true}]
is_method: true
is_public: true
receiver_typ: typ.name
})
elm_type := typ.name.right(6)
elm_type2 := p.table.find_type(elm_type)
if p.typ_to_fmt(elm_type, 0) == '' &&
!p.table.type_has_method(elm_type2, 'str') {
p.error('cant print ${elm_type}[], unhandled print of ${elm_type}')
}
p.v.vgen_buf.writeln('
fn (a $typ.name) str() string {
mut sb := strings.new_builder(a.len * 3)
sb.write("[")
for i, elm in a {
sb.write(elm.str())
if i < a.len - 1 {
sb.write(", ")
}
}
sb.write("]")
return sb.str()
}
')
p.cgen.fns << 'string ${typ.name}_str();'
}
// `Foo { bar: 3, baz: 'hi' }` => '{ bar: 3, baz: "hi" }'
fn (p mut Parser) gen_struct_str(typ Type) {
p.add_method(typ.name, Fn{
name: 'str'
typ: 'string'
args: [Var{typ: typ.name, is_arg:true}]
is_method: true
is_public: true
receiver_typ: typ.name
})
mut sb := strings.new_builder(typ.fields.len * 20)
sb.writeln('fn (a $typ.name) str() string {\nreturn')
sb.writeln("'{")
for field in typ.fields {
sb.writeln('\t$field.name: $' + 'a.${field.name}')
}
sb.writeln("}'")
sb.writeln('}')
p.v.vgen_buf.writeln(sb.str())
// Need to manually add the definition to `fns` so that it stays
// at the top of the file.
// This function will get parsee by V after the main pass.
p.cgen.fns << 'string ${typ.name}_str();'
}

130
vlib/compiler/depgraph.v Normal file
View File

@ -0,0 +1,130 @@
// 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.
// Directed acyclic graph
// this implementation is specifically suited to ordering dependencies
module compiler
struct DepGraphNode {
mut:
name string
deps []string
last_cycle string
}
struct DepGraph {
pub:
mut:
acyclic bool
nodes []DepGraphNode
}
struct DepSet {
mut:
items []string
}
pub fn(dset mut DepSet) add(item string) {
dset.items << item
}
pub fn(dset &DepSet) diff(otherset DepSet) DepSet {
mut diff := DepSet{}
for item in dset.items {
if !item in otherset.items {
diff.items << item
}
}
return diff
}
pub fn(dset &DepSet) size() int {
return dset.items.len
}
pub fn new_dep_graph() &DepGraph {
return &DepGraph{
acyclic: true
}
}
pub fn(graph mut DepGraph) add(mod string, deps []string) {
graph.nodes << DepGraphNode{
name: mod,
deps: deps.clone()
}
}
pub fn(graph &DepGraph) resolve() &DepGraph {
mut node_names := map[string]DepGraphNode
mut node_deps := map[string]DepSet
for _, node in graph.nodes {
node_names[node.name] = node
mut dep_set := DepSet{}
for _, dep in node.deps {
dep_set.add(dep)
}
node_deps[node.name] = dep_set
}
mut resolved := new_dep_graph()
for node_deps.size != 0 {
mut ready_set := DepSet{}
for name, deps in node_deps {
if deps.size() == 0 {
ready_set.add(name)
}
}
if ready_set.size() == 0 {
mut g := new_dep_graph()
g.acyclic = false
ndk := node_deps.keys()
for name, _ in node_deps {
mut node := node_names[name]
if name == ndk[node_deps.size-1] {
node.last_cycle = node_deps[name].items[node_deps[name].items.len-1]
}
g.nodes << node
}
return g
}
for name in ready_set.items {
node_deps.delete(name)
resolved.nodes << node_names[name]
}
for name, deps in node_deps {
node_deps[name] = deps.diff(ready_set)
}
}
return resolved
}
pub fn(graph &DepGraph) last_node() DepGraphNode {
return graph.nodes[graph.nodes.len-1]
}
pub fn(graph &DepGraph) last_cycle() string {
return graph.last_node().last_cycle
}
pub fn(graph &DepGraph) display() {
for i:=0; i<graph.nodes.len; i++ {
node := graph.nodes[i]
for dep in node.deps {
mut out := ' * $node.name -> $dep'
if !graph.acyclic && i == graph.nodes.len-1 && dep == node.last_cycle {
out += ' <-- last cycle'
}
println(out)
}
}
}

1161
vlib/compiler/fn.v Normal file

File diff suppressed because it is too large Load Diff

556
vlib/compiler/gen_c.v Normal file
View File

@ -0,0 +1,556 @@
module compiler
import strings
const (
dot_ptr = '->'
)
/*
fn (p mut Parser) gen_or_else(pos int) string {
}
*/
// returns the type of the new variable
fn (p mut Parser) gen_var_decl(name string, is_static bool) string {
// Generate expression to tmp because we need its type first
// `[typ] [name] = bool_expression();`
pos := p.cgen.add_placeholder()
mut typ := p.bool_expression()
if typ.starts_with('...') { typ = typ.right(3) }
//p.gen('/*after expr*/')
// Option check ? or {
or_else := p.tok == .key_orelse
tmp := p.get_tmp()
if or_else {
// Option_User tmp = get_user(1);
// if (!tmp.ok) { or_statement }
// User user = *(User*)tmp.data;
// p.assigned_var = ''
p.cgen.set_placeholder(pos, '$typ $tmp = ')
p.genln(';')
if !typ.starts_with('Option_') {
p.error('`or` block cannot be applied to non-optional type')
}
typ = typ.replace('Option_', '')
p.next()
p.check(.lcbr)
p.genln('if (!$tmp .ok) {')
p.register_var(Var {
name: 'err'
typ: 'string'
is_mut: false
is_used: true
})
p.genln('string err = $tmp . error;')
p.statements()
p.genln('$typ $name = *($typ*) $tmp . data;')
if !p.returns && p.prev_tok2 != .key_continue && p.prev_tok2 != .key_break {
p.error('`or` block must return/exit/continue/break/panic')
}
p.returns = false
return typ
}
gen_name := p.table.var_cgen_name(name)
mut nt_gen := p.table.cgen_name_type_pair(gen_name, typ)
// `foo := C.Foo{}` => `Foo foo;`
if !p.is_empty_c_struct_init && !typ.starts_with('['){
nt_gen += '='
} else if typ.starts_with('[') && typ[ typ.len-1 ] != `*` {
// a fixed_array initializer, like `v := [1.1, 2.2]!!`
// ... should translate to the following in C `f32 v[2] = {1.1, 2.2};`
initializer := p.cgen.cur_line.right(pos)
if initializer.len > 0 {
p.cgen.resetln(' = {' + initializer.all_after('{') )
} else if initializer.len == 0 {
p.cgen.resetln(' = { 0 }')
}
}
if is_static {
nt_gen = 'static $nt_gen'
}
p.cgen.set_placeholder(pos, nt_gen)
return typ
}
fn (p mut Parser) gen_fn_decl(f Fn, typ, str_args string) {
dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so {
'__declspec(dllexport) '
} else if p.attr == 'inline' {
'static inline '
} else {
''
}
fn_name_cgen := p.table.fn_gen_name(f)
//str_args := f.str_args(p.table)
p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {')
}
// blank identifer assignment `_ = 111`
fn (p mut Parser) gen_blank_identifier_assign() {
assign_error_tok_idx := p.token_idx
p.check_name()
p.check_space(.assign)
expr := p.lit
is_indexer := p.peek() == .lsbr
is_fn_call := p.peek() == .lpar || (p.peek() == .dot && p.tokens[p.token_idx+2].tok == .lpar)
if !is_indexer && !is_fn_call {
p.error_with_token_index('assigning `$expr` to `_` is redundant', assign_error_tok_idx)
}
pos := p.cgen.add_placeholder()
mut typ := p.bool_expression()
tmp := p.get_tmp()
// handle or
if p.tok == .key_orelse {
p.cgen.set_placeholder(pos, '$typ $tmp = ')
p.genln(';')
typ = typ.replace('Option_', '')
p.next()
p.check(.lcbr)
p.genln('if (!$tmp .ok) {')
p.register_var(Var {
name: 'err'
typ: 'string'
is_mut: false
is_used: true
})
p.genln('string err = $tmp . error;')
p.statements()
p.returns = false
} else {
if is_fn_call {
p.gen(';')
} else {
p.cgen.resetln('{$typ _ = $p.cgen.cur_line;}')
}
}
}
fn types_to_c(types []Type, table &Table) string {
mut sb := strings.new_builder(10)
for t in types {
if t.cat != .union_ && t.cat != .struct_ && t.cat != .objc_interface {
continue
}
//if is_atomic {
//sb.write('_Atomic ')
//}
if t.cat == .objc_interface {
sb.writeln('@interface $t.name : $t.parent { @public')
}
else {
kind := if t.cat == .union_ {'union'} else {'struct'}
sb.writeln('$kind $t.name {')
}
for field in t.fields {
sb.write('\t')
sb.writeln(table.cgen_name_type_pair(field.name,
field.typ) + ';')
}
sb.writeln('};\n')
if t.cat == .objc_interface {
sb.writeln('@end')
}
}
return sb.str()
}
fn (p mut Parser) index_get(typ string, fn_ph int, cfg IndexCfg) {
// Erase var name we generated earlier: "int a = m, 0"
// "m, 0" gets killed since we need to start from scratch. It's messy.
// "m, 0" is an index expression, save it before deleting and insert later in map_get()
mut index_expr := ''
if p.cgen.is_tmp {
index_expr = p.cgen.tmp_line.right(fn_ph)
p.cgen.resetln(p.cgen.tmp_line.left(fn_ph))
} else {
index_expr = p.cgen.cur_line.right(fn_ph)
p.cgen.resetln(p.cgen.cur_line.left(fn_ph))
}
// Can't pass integer literal, because map_get() requires a void*
tmp := p.get_tmp()
tmp_ok := p.get_tmp()
if cfg.is_map {
p.gen('$tmp')
def := type_default(typ)
p.cgen.insert_before('$typ $tmp = $def; ' +
'bool $tmp_ok = map_get(/*$p.file_name : $p.scanner.line_nr*/$index_expr, & $tmp);')
}
else if cfg.is_arr {
if p.pref.translated && !p.builtin_mod {
p.gen('$index_expr ]')
}
else {
if cfg.is_ptr {
p.gen('( *($typ*) array_get(* $index_expr) )')
} else {
p.gen('( *($typ*) array_get($index_expr) )')
}
}
}
else if cfg.is_str && !p.builtin_mod {
p.gen('string_at($index_expr)')
}
// Zero the string after map_get() if it's nil, numbers are automatically 0
// This is ugly, but what can I do without generics?
// TODO what about user types?
if cfg.is_map && typ == 'string' {
// p.cgen.insert_before('if (!${tmp}.str) $tmp = tos("", 0);')
p.cgen.insert_before('if (!$tmp_ok) $tmp = tos((byte *)"", 0);')
}
}
fn (table mut Table) fn_gen_name(f &Fn) string {
mut name := f.name
if f.is_method {
name = '${f.receiver_typ}_$f.name'
name = name.replace(' ', '')
name = name.replace('*', '')
name = name.replace('+', 'plus')
name = name.replace('-', 'minus')
}
// Avoid name conflicts (with things like abs(), print() etc).
// Generate v_abs(), v_print()
// TODO duplicate functionality
if f.mod == 'builtin' && f.name in CReserved {
return 'v_$name'
}
// Obfuscate but skip certain names
// TODO ugly, fix
// NB: the order here is from faster to potentially slower checks
if table.obfuscate &&
!f.is_c &&
f.name != 'main' && f.name != 'WinMain' && f.name != 'main__main' &&
f.name != 'gg__vec2' &&
f.name != 'build_token_str' &&
f.name != 'build_keys' &&
f.mod != 'builtin' &&
f.mod != 'darwin' &&
f.mod != 'os' &&
f.mod != 'json' &&
!f.name.ends_with('_init') &&
!f.name.contains('window_proc') &&
!name.ends_with('_str') &&
!name.contains('contains') {
mut idx := table.obf_ids[name]
// No such function yet, register it
if idx == 0 {
table.fn_cnt++
table.obf_ids[name] = table.fn_cnt
idx = table.fn_cnt
}
old := name
name = 'f_$idx'
println('$old ==> $name')
}
return name
}
fn (p mut Parser) gen_method_call(receiver_type, ftyp string, cgen_name string, receiver Var,method_ph int) {
//mut cgen_name := p.table.fn_gen_name(f)
mut method_call := cgen_name + '('
// if receiver is key_mut or a ref (&), generate & for the first arg
if receiver.ref || (receiver.is_mut && !receiver_type.contains('*')) {
method_call += '& /* ? */'
}
// generate deref (TODO copy pasta later in fn_call_args)
if !receiver.is_mut && receiver_type.contains('*') {
method_call += '*'
}
mut cast := ''
// Method returns (void*) => cast it to int, string, user etc
// number := *(int*)numbers.first()
if ftyp == 'void*' {
if receiver_type.starts_with('array_') {
// array_int => int
cast = receiver_type.all_after('array_')
cast = '*($cast*) '
}else{
cast = '(voidptr) '
}
}
p.cgen.set_placeholder(method_ph, '$cast $method_call')
//return method_call
}
fn (p mut Parser) gen_array_at(typ_ string, is_arr0 bool, fn_ph int) {
mut typ := typ_
//p.fgen('[')
// array_int a; a[0]
// type is "array_int", need "int"
// typ = typ.replace('array_', '')
if is_arr0 {
typ = typ.right(6)
}
// array a; a.first() voidptr
// type is "array", need "void*"
if typ == 'array' {
typ = 'void*'
}
// No bounds check in translated from C code
if p.pref.translated && !p.builtin_mod {
// Cast void* to typ*: add (typ*) to the beginning of the assignment :
// ((int*)a.data = ...
p.cgen.set_placeholder(fn_ph, '(($typ*)(')
p.gen('.data))[')
}
else {
p.gen(',')
}
}
fn (p mut Parser) gen_for_header(i, tmp, var_typ, val string) {
p.genln('for (int $i = 0; $i < ${tmp}.len; $i++) {')
if val == '_' { return }
p.genln('$var_typ $val = (($var_typ *) $tmp . data)[$i];')
}
fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) {
p.genln('array_byte bytes_$tmp = string_bytes( $tmp );')
p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {')
if val == '_' { return }
p.genln('$var_typ $val = (($var_typ *) bytes_$tmp . data)[$i];')
}
fn (p mut Parser) gen_for_range_header(i, range_end, tmp, var_type, val string) {
p.genln(';\nfor (int $i = $tmp; $i < $range_end; $i++) {')
if val == '_' { return }
p.genln('$var_type $val = $i;')
}
fn (p mut Parser) gen_for_map_header(i, tmp, var_typ, val, typ string) {
def := type_default(typ)
p.genln('array_string keys_$tmp = map_keys(& $tmp ); ')
p.genln('for (int l = 0; l < keys_$tmp .len; l++) {')
p.genln('string $i = ((string*)keys_$tmp .data)[l];')
// TODO don't call map_get() for each key, fetch values while traversing
// the tree (replace `map_keys()` above with `map_key_vals()`)
if val == '_' { return }
p.genln('$var_typ $val = $def; map_get($tmp, $i, & $val);')
}
fn (p mut Parser) gen_for_varg_header(i, varg, var_typ, val string) {
p.genln('for (int $i = 0; $i < ${varg}->len; $i++) {')
if val == '_' { return }
p.genln('$var_typ $val = (($var_typ *) $varg->args)[$i];')
}
fn (p mut Parser) gen_array_init(typ string, no_alloc bool, new_arr_ph int, nr_elems int) {
mut new_arr := 'new_array_from_c_array'
if no_alloc {
new_arr += '_no_alloc'
}
if nr_elems == 0 {
p.gen(' TCCSKIP(0) })')
} else {
p.gen(' })')
}
// Need to do this in the second pass, otherwise it goes to the very top of the out.c file
if !p.first_pass() {
p.cgen.set_placeholder(new_arr_ph,
'$new_arr($nr_elems, $nr_elems, sizeof($typ), EMPTY_ARRAY_OF_ELEMS( $typ, $nr_elems ) { ')
}
}
fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool,fn_ph, assign_pos int, is_cao bool) {
// `a[0] = 7`
// curline right now: `a , 0 = 7`
mut val := p.cgen.cur_line.right(assign_pos)
p.cgen.resetln(p.cgen.cur_line.left(assign_pos))
mut cao_tmp := p.cgen.cur_line
mut func := ''
if is_map {
func = 'map_set(&'
// CAO on map is a bit more complicated as it loads
// the value inside a pointer instead of returning it.
}
else {
if is_ptr {
func = 'array_set('
if is_cao {
cao_tmp = '*($p.expected_type *) array_get(*$cao_tmp)'
}
}
else {
func = 'array_set(&/*q*/'
if is_cao {
cao_tmp = '*($p.expected_type *) array_get($cao_tmp)'
}
}
}
p.cgen.set_placeholder(fn_ph, func)
if is_cao {
val = cao_tmp + val.all_before('=') + val.all_after('=')
}
p.gen(', & ($typ []) { $val })')
}
// returns true in case of an early return
fn (p mut Parser) gen_struct_init(typ string, t Type) bool {
// 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 p.is_c_struct_init {
if t.cat != .c_typedef {
p.cgen.insert_before('struct /*c struct init*/')
}
}
// TODO tm struct struct bug
if typ == 'tm' {
p.cgen.lines[p.cgen.lines.len-1] = ''
}
p.next()
p.check(.lcbr)
ptr := typ.contains('*')
// `user := User{foo:bar}` => `User user = (User){ .foo = bar}`
if !ptr {
if p.is_c_struct_init {
// `face := C.FT_Face{}` => `FT_Face face;`
if p.tok == .rcbr {
p.is_empty_c_struct_init = true
p.check(.rcbr)
return true
}
p.gen('(struct $typ) {')
p.is_c_struct_init = false
}
else {
p.gen('($typ) {')
}
}
else {
// TODO tmp hack for 0 pointers init
// &User{!} ==> 0
if p.tok == .not {
p.next()
p.gen('0')
p.check(.rcbr)
return true
}
p.gen('($t.name*)memdup(&($t.name) {')
}
return false
}
fn (p mut Parser) gen_struct_field_init(field string) {
p.gen('.$field = ')
}
fn (p mut Parser) gen_empty_map(typ string) {
p.gen('new_map(1, sizeof($typ))')
}
fn (p mut Parser) cast(typ string) {
p.next()
pos := p.cgen.add_placeholder()
if p.tok == .rpar {
// skip `)` if it's `(*int)(ptr)`, not `int(a)`
p.ptr_cast = true
p.next()
}
p.check(.lpar)
p.expected_type = typ
expr_typ := p.bool_expression()
// `face := FT_Face(cobj)` => `FT_Face face = *((FT_Face*)cobj);`
casting_voidptr_to_value := expr_typ == 'void*' && typ != 'int' &&
typ != 'byteptr' && !typ.ends_with('*')
p.expected_type = ''
// `string(buffer)` => `tos2(buffer)`
// `string(buffer, len)` => `tos(buffer, len)`
// `string(bytes_array, len)` => `tos(bytes_array.data, len)`
is_byteptr := expr_typ == 'byte*' || expr_typ == 'byteptr'
is_bytearr := expr_typ == 'array_byte'
if typ == 'string' {
if is_byteptr || is_bytearr {
if p.tok == .comma {
p.check(.comma)
p.cgen.set_placeholder(pos, 'tos((byte *)')
if is_bytearr {
p.gen('.data')
}
p.gen(', ')
p.check_types(p.expression(), 'int')
} else {
if is_bytearr {
p.gen('.data')
}
p.cgen.set_placeholder(pos, 'tos2((byte *)')
}
}
// `string(234)` => error
else if expr_typ == 'int' {
p.error('cannot cast `$expr_typ` to `$typ`, use `str()` method instead')
}
else {
p.error('cannot cast `$expr_typ` to `$typ`')
}
}
else if typ == 'byte' && expr_typ == 'string' {
p.error('cannot cast `$expr_typ` to `$typ`, use backquotes `` to create a `$typ` or access the value of an index of `$expr_typ` using []')
}
else if casting_voidptr_to_value {
p.cgen.set_placeholder(pos, '*($typ*)(')
}
else {
p.cgen.set_placeholder(pos, '($typ)(')
}
p.check(.rpar)
p.gen(')')
}
fn type_default(typ string) string {
if typ.starts_with('array_') {
return 'new_array(0, 1, sizeof( ${typ.right(6)} ))'
}
// Always set pointers to 0
if typ.ends_with('*') {
return '0'
}
// User struct defined in another module.
if typ.contains('__') {
return '{0}'
}
// Default values for other types are not needed because of mandatory initialization
switch typ {
case 'bool': return '0'
case 'string': return 'tos((byte *)"", 0)'
case 'i8': return '0'
case 'i16': return '0'
case 'i64': return '0'
case 'u16': return '0'
case 'u32': return '0'
case 'u64': return '0'
case 'byte': return '0'
case 'int': return '0'
case 'rune': return '0'
case 'f32': return '0.0'
case 'f64': return '0.0'
case 'byteptr': return '0'
case 'voidptr': return '0'
}
return '{0}'
}
fn (p mut Parser) gen_array_push(ph int, typ, expr_type, tmp, elm_type string) {
// Two arrays of the same type?
push_array := typ == expr_type
if push_array {
p.cgen.set_placeholder(ph, '_PUSH_MANY(&' )
p.gen('), $tmp, $typ)')
} else {
p.check_types(expr_type, elm_type)
// Pass tmp var info to the _PUSH macro
// Prepend tmp initialisation and push call
// Don't dereference if it's already a mutable array argument (`fn foo(mut []int)`)
push_call := if typ.contains('*'){'_PUSH('} else { '_PUSH(&'}
p.cgen.set_placeholder(ph, push_call)
if elm_type.ends_with('*') {
p.gen('), $tmp, ${elm_type.left(elm_type.len - 1)})')
} else {
p.gen('), $tmp, $elm_type)')
}
}
}

249
vlib/compiler/gen_js.v Normal file
View File

@ -0,0 +1,249 @@
module compiler
import strings
const (
dot_ptr = '.'
)
fn (p mut Parser) gen_var_decl(name string, is_static bool) string {
p.gen('var $name /* typ */ = ')
mut typ := p.bool_expression()
if typ.starts_with('...') { typ = typ.right(3) }
or_else := p.tok == .key_orelse
//tmp := p.get_tmp()
if or_else {
//panic('optionals todo')
}
return typ
}
fn (p mut Parser) gen_fn_decl(f Fn, typ, _str_args string) {
mut str_args := ''
for i, arg in f.args {
str_args += ' /** @type { $arg.typ } **/ ' + arg.name
if i < f.args.len - 1 {
str_args += ', '
}
}
name := p.table.fn_gen_name(f)
if f.is_method {
p.genln('\n${f.receiver_typ}.prototype.${name} = function($str_args) {')
} else {
p.genln('/** @return { $typ } **/\nfunction $name($str_args) {')
}
}
fn (p mut Parser) gen_blank_identifier_assign() {
assign_error_tok_idx := p.token_idx
p.check_name()
p.check_space(.assign)
expr := p.lit
is_indexer := p.peek() == .lsbr
is_fn_call := p.peek() == .lpar || (p.peek() == .dot && p.tokens[p.token_idx+2].tok == .lpar)
if !is_indexer && !is_fn_call {
p.error_with_token_index('assigning `$expr` to `_` is redundant', assign_error_tok_idx)
}
p.bool_expression()
or_else := p.tok == .key_orelse
//tmp := p.get_tmp()
if or_else {
//panic('optionals todo')
}
}
fn types_to_c(types []Type, table &Table) string {
mut sb := strings.new_builder(10)
for t in types {
if t.cat != .union_ && t.cat != .struct_ {
continue
}
sb.write('\n/**\n')
sb.write('* @typedef { object } $t.name' + 'Type\n')
for field in t.fields {
sb.writeln('* @property { $field.typ' + '= } $field.name')
}
sb.writeln('**/\n')
sb.writeln('/** @type { function & $t.name' + 'Type } **/')
sb.writeln('var $t.name = function() {}')
}
return sb.str()
}
fn (p mut Parser) index_get(typ string, fn_ph int, cfg IndexCfg) {
p.cgen.cur_line = p.cgen.cur_line.replace(',', '[') + ']'
}
fn (table &Table) fn_gen_name(f &Fn) string {
mut name := f.name
if f.is_method {
name = name.replace(' ', '')
name = name.replace('*', '')
name = name.replace('+', 'plus')
name = name.replace('-', 'minus')
return name
}
// Avoid name conflicts (with things like abs(), print() etc).
// Generate b_abs(), b_print()
// TODO duplicate functionality
if f.mod == 'builtin' && f.name in CReserved {
return 'v_$name'
}
return name
}
fn (p mut Parser) gen_method_call(receiver_type, ftyp string, cgen_name string, receiver Var,method_ph int) {
//mut cgen_name := p.table.fn_gen_name(f)
//mut method_call := cgen_name + '('
p.gen('.' + cgen_name.all_after('_') + '(')
//p.cgen.set_placeholder(method_ph, '$cast kKE $method_call')
//return method_call
}
fn (p mut Parser) gen_array_at(typ string, is_arr0 bool, fn_ph int) {
p.gen('[')
}
fn (p mut Parser) gen_for_header(i, tmp, var_typ, val string) {
p.genln('for (var $i = 0; $i < ${tmp}.length; $i++) {')
if val == '_' { return }
p.genln('var $val = $tmp [$i];')
}
fn (p mut Parser) gen_for_range_header(i, range_end, tmp, var_type, val string) {
p.genln(';\nfor (var $i = $tmp; $i < $range_end; $i++) {')
if val == '_' { return }
p.genln('var /*$var_type*/ $val = $i;')
}
fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) {
p.genln('for (var $i = 0; $i < $tmp .length; $i ++) {')
if val == '_' { return }
p.genln('var $val = $tmp[$i];')
}
fn (p mut Parser) gen_for_map_header(i, tmp, var_typ, val, typ string) {
p.genln('for (var $i in $tmp) {')
if val == '_' { return }
p.genln('var $val = $tmp[$i];')
}
fn (p mut Parser) gen_for_varg_header(i, varg, var_typ, val string) {
p.genln('for (var $i = 0; $i < ${varg}.len; $i++) {')
if val == '_' { return }
p.genln('var $val = ${varg}.args[$i];')
}
fn (p mut Parser) gen_array_init(typ string, no_alloc bool, new_arr_ph int, nr_elems int) {
p.cgen.set_placeholder(new_arr_ph, '[')
p.gen(']')
}
fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool,fn_ph, assign_pos int, is_cao bool) {
mut val := p.cgen.cur_line.right(assign_pos)
p.cgen.resetln(p.cgen.cur_line.left(assign_pos))
p.gen('] =')
cao_tmp := p.cgen.cur_line
if is_cao {
val = cao_tmp + val.all_before('=') + val.all_after('=')
}
p.gen(val)
}
// returns true in case of an early return
fn (p mut Parser) gen_struct_init(typ string, t Type) bool {
p.next()
p.check(.lcbr)
ptr := typ.contains('*')
if !ptr {
p.gen('{')
}
else {
// TODO tmp hack for 0 pointers init
// &User{!} ==> 0
if p.tok == .not {
p.next()
p.gen('}')
p.check(.rcbr)
return true
}
}
return false
}
fn (p mut Parser) gen_struct_field_init(field string) {
p.gen('$field : ')
}
fn (p mut Parser) gen_empty_map(typ string) {
p.gen('{}')
}
fn (p mut Parser) cast(typ string) string {
p.next()
pos := p.cgen.add_placeholder()
if p.tok == .rpar {
p.next()
}
p.check(.lpar)
p.bool_expression()
if typ == 'string' {
if p.tok == .comma {
p.check(.comma)
p.cgen.set_placeholder(pos, 'tos(')
//p.gen('tos(')
p.gen(', ')
p.expression()
p.gen(')')
}
}
p.check(.rpar)
return typ
}
fn type_default(typ string) string {
if typ.starts_with('array_') {
return '[]'
}
// Always set pointers to 0
if typ.ends_with('*') {
return '0'
}
// User struct defined in another module.
if typ.contains('__') {
return '{}'
}
// Default values for other types are not needed because of mandatory initialization
switch typ {
case 'bool': return '0'
case 'string': return '""'
case 'i8': return '0'
case 'i16': return '0'
case 'i64': return '0'
case 'u16': return '0'
case 'u32': return '0'
case 'u64': return '0'
case 'byte': return '0'
case 'int': return '0'
case 'rune': return '0'
case 'f32': return '0.0'
case 'f64': return '0.0'
case 'byteptr': return '0'
case 'voidptr': return '0'
}
return '{}'
}
fn (p mut Parser) gen_array_push(ph int, typ, expr_type, tmp, tmp_typ string) {
push_array := typ == expr_type
if push_array {
p.cgen.set_placeholder(ph, 'push(&' )
p.gen('), $tmp, $typ)')
} else {
p.check_types(expr_type, tmp_typ)
p.gen(')')
p.cgen.cur_line = p.cgen.cur_line.replace(',', '.push')
}
}

175
vlib/compiler/jsgen.v Normal file
View File

@ -0,0 +1,175 @@
// 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 compiler
// TODO replace with comptime code generation.
// TODO remove cJSON dependency.
// OLD: User decode_User(string js) {
// now it's
// User decode_User(cJSON* root) {
// User res;
// res.name = decode_string(js_get(root, "name"));
// res.profile = decode_Profile(js_get(root, "profile"));
// return res;
// }
// Codegen json_decode/encode funcs
fn (p mut Parser) gen_json_for_type(typ Type) {
mut dec := ''
mut enc := ''
t := typ.name
if t == 'int' || t == 'string' || t == 'bool' {
return
}
if p.first_pass() {
return
}
// println('gen_json_for_type( $typ.name )')
// Register decoder fn
mut dec_fn := Fn {
mod: p.mod
typ: 'Option_$typ.name'
name: js_dec_name(t)
}
// Already registered? Skip.
if p.table.known_fn(dec_fn.name) {
return
}
// decode_TYPE funcs receive an actual cJSON* object to decode
// cJSON_Parse(str) call is added by the compiler
arg := Var {
typ: 'cJSON*'
}
dec_fn.args << arg
p.table.register_fn(dec_fn)
// Register encoder fn
mut enc_fn := Fn {
mod: p.mod
typ: 'cJSON*'
name: js_enc_name(t)
}
// encode_TYPE funcs receive an object to encode
enc_arg := Var {
typ: t
}
enc_fn.args << enc_arg
p.table.register_fn(enc_fn)
// Code gen decoder
dec += '
//$t $dec_fn.name(cJSON* root) {
Option $dec_fn.name(cJSON* root, $t* res) {
// $t res;
if (!root) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
fprintf(stderr, "Error in decode() for $t error_ptr=: %%s\\n", error_ptr);
// printf("\\nbad js=%%s\\n", js.str);
return v_error(tos2(error_ptr));
}
}
'
// Code gen encoder
enc += '
cJSON* $enc_fn.name($t val) {
cJSON *o = cJSON_CreateObject();
string res = tos2("");
'
// Handle arrays
if t.starts_with('array_') {
dec += p.decode_array(t)
enc += p.encode_array(t)
}
// Range through fields
for field in typ.fields {
if field.attr == 'skip' {
continue
}
name := if field.attr.starts_with('json:') {
field.attr.right(5)
} else {
field.name
}
field_type := p.table.find_type(field.typ)
_typ := field.typ.replace('*', '')
enc_name := js_enc_name(_typ)
if field.attr == 'raw' {
dec += ' res->$field.name = tos2(cJSON_PrintUnformatted(' +
'js_get(root, "$name")));\n'
} else {
// Now generate decoders for all field types in this struct
// need to do it here so that these functions are generated first
p.gen_json_for_type(field_type)
dec_name := js_dec_name(_typ)
if is_js_prim(_typ) {
dec += ' res->$field.name = $dec_name(js_get(' +
'root, "$name"))'
}
else {
dec += ' $dec_name(js_get(root, "$name"), & (res->$field.name))'
}
dec += ';\n'
}
enc += ' cJSON_AddItemToObject(o, "$name",$enc_name(val.$field.name)); \n'
}
// cJSON_delete
//p.cgen.fns << '$dec return opt_ok(res); \n}'
p.cgen.fns << '$dec return opt_ok(res, sizeof(*res)); \n}'
p.cgen.fns << '/*enc start*/ $enc return o;}'
}
fn is_js_prim(typ string) bool {
return typ == 'int' || typ == 'string' ||
typ == 'bool' || typ == 'f32' || typ == 'f64' ||
typ == 'i8' || typ == 'i16' || typ == 'i64' ||
typ == 'u16' || typ == 'u32' || typ == 'u64'
}
fn (p mut Parser) decode_array(array_type string) string {
typ := array_type.replace('array_', '')
t := p.table.find_type(typ)
fn_name := js_dec_name(typ)
// If we have `[]Profile`, have to register a Profile en(de)coder first
p.gen_json_for_type(t)
mut s := ''
if is_js_prim(typ) {
s = '$typ val= $fn_name(jsval); '
}
else {
s = ' $typ val; $fn_name(jsval, &val); '
}
return '
*res = new_array(0, 0, sizeof($typ));
const cJSON *jsval = NULL;
cJSON_ArrayForEach(jsval, root)
{
$s
array_push(res, &val);
}
'
}
fn js_enc_name(typ string) string {
name := 'json__jsencode_$typ'
return name
}
fn js_dec_name(typ string) string {
name := 'json__jsdecode_$typ'
return name
}
fn (p &Parser) encode_array(array_type string) string {
typ := array_type.replace('array_', '')
fn_name := js_enc_name(typ)
return '
o = cJSON_CreateArray();
for (int i = 0; i < val.len; i++){
cJSON_AddItemToArray(o, $fn_name( (($typ*)val.data)[i] ));
}
'
}

220
vlib/compiler/live.v Normal file
View File

@ -0,0 +1,220 @@
module compiler
import os
import time
fn (v &V) generate_hotcode_reloading_compiler_flags() []string {
mut a := []string
if v.pref.is_live || v.pref.is_so {
// See 'man dlopen', and test running a GUI program compiled with -live
if (v.os == .linux || os.user_os() == 'linux'){
a << '-rdynamic'
}
if (v.os == .mac || os.user_os() == 'mac'){
a << '-flat_namespace'
}
}
return a
}
fn (v &V) generate_hotcode_reloading_declarations() {
mut cgen := v.cgen
if v.os != .windows && v.os != .msvc {
if v.pref.is_so {
cgen.genln('pthread_mutex_t live_fn_mutex;')
}
if v.pref.is_live {
cgen.genln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;')
}
} else {
if v.pref.is_so {
cgen.genln('HANDLE live_fn_mutex;')
}
if v.pref.is_live {
cgen.genln('HANDLE live_fn_mutex = 0;')
}
}
}
fn (v &V) generate_hotcode_reloading_main_caller() {
if !v.pref.is_live { return }
// We are in live code reload mode, so start the .so loader in the background
mut cgen := v.cgen
cgen.genln('')
file_base := os.filename(v.dir).replace('.v', '')
if !(v.os == .windows || v.os == .msvc) {
// unix:
so_name := file_base + '.so'
cgen.genln(' char *live_library_name = "$so_name";')
cgen.genln(' load_so(live_library_name);')
cgen.genln(' pthread_t _thread_so;')
cgen.genln(' pthread_create(&_thread_so , NULL, &reload_so, live_library_name);')
} else {
// windows:
so_name := file_base + if v.os == .msvc {'.dll'} else {'.so'}
cgen.genln(' char *live_library_name = "$so_name";')
cgen.genln(' live_fn_mutex = CreateMutexA(0, 0, 0);')
cgen.genln(' load_so(live_library_name);')
cgen.genln(' unsigned long _thread_so;')
cgen.genln(' _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);')
}
}
fn (v &V) generate_hot_reload_code() {
mut cgen := v.cgen
// Hot code reloading
if v.pref.is_live {
mut file := os.realpath(v.dir)
file_base := os.filename(file).replace('.v', '')
so_name := file_base + '.so'
// Need to build .so file before building the live application
// The live app needs to load this .so file on initialization.
mut vexe := os.args[0]
if os.user_os() == 'windows' {
vexe = cescaped_path(vexe)
file = cescaped_path(file)
}
mut msvc := ''
if v.os == .msvc {
msvc = '-os msvc'
}
so_debug_flag := if v.pref.is_debug { '-g' } else { '' }
cmd_compile_shared_library := '$vexe $msvc $so_debug_flag -o $file_base -shared $file'
if v.pref.show_c_cmd {
println(cmd_compile_shared_library)
}
ticks := time.ticks()
os.system(cmd_compile_shared_library)
diff := time.ticks() - ticks
println('compiling shared library took $diff ms')
println('=========\n')
cgen.genln('
void lfnmutex_print(char *s){
if(0){
fflush(stderr);
fprintf(stderr,">> live_fn_mutex: %p | %s\\n", &live_fn_mutex, s);
fflush(stderr);
}
}
')
if v.os != .windows && v.os != .msvc {
cgen.genln('
#include <dlfcn.h>
void* live_lib=0;
int load_so(byteptr path) {
char cpath[1024];
sprintf(cpath,"./%s", path);
//printf("load_so %s\\n", cpath);
if (live_lib) dlclose(live_lib);
live_lib = dlopen(cpath, RTLD_LAZY);
if (!live_lib) {
puts("open failed");
exit(1);
return 0;
}
')
for so_fn in cgen.so_fns {
cgen.genln('$so_fn = dlsym(live_lib, "$so_fn"); ')
}
}
else {
cgen.genln('
void pthread_mutex_lock(HANDLE *m) {
WaitForSingleObject(*m, INFINITE);
}
void pthread_mutex_unlock(HANDLE *m) {
ReleaseMutex(*m);
}
void* live_lib=0;
int load_so(byteptr path) {
char cpath[1024];
sprintf(cpath, "./%s", path);
if (live_lib) FreeLibrary(live_lib);
live_lib = LoadLibraryA(cpath);
if (!live_lib) {
puts("open failed");
exit(1);
return 0;
}
')
for so_fn in cgen.so_fns {
cgen.genln('$so_fn = (void *)GetProcAddress(live_lib, "$so_fn"); ')
}
}
cgen.genln('return 1;
}
int _live_reloads = 0;
void reload_so() {
char new_so_base[1024];
char new_so_name[1024];
char compile_cmd[1024];
int last = os__file_last_mod_unix(tos2("$file"));
while (1) {
// TODO use inotify
int now = os__file_last_mod_unix(tos2("$file"));
if (now != last) {
last = now;
_live_reloads++;
//v -o bounce -shared bounce.v
sprintf(new_so_base, ".tmp.%d.${file_base}", _live_reloads);
#ifdef _WIN32
// We have to make this directory becuase windows WILL NOT
// do it for us
os__mkdir(string_all_before_last(tos2(new_so_base), tos2("/")));
#endif
#ifdef _MSC_VER
sprintf(new_so_name, "%s.dll", new_so_base);
#else
sprintf(new_so_name, "%s.so", new_so_base);
#endif
sprintf(compile_cmd, "$vexe $msvc -o %s -shared $file", new_so_base);
os__system(tos2(compile_cmd));
if( !os__file_exists(tos2(new_so_name)) ) {
fprintf(stderr, "Errors while compiling $file\\n");
continue;
}
lfnmutex_print("reload_so locking...");
pthread_mutex_lock(&live_fn_mutex);
lfnmutex_print("reload_so locked");
live_lib = 0; // hack: force skipping dlclose/1, the code may be still used...
load_so(new_so_name);
#ifndef _WIN32
unlink(new_so_name); // removing the .so file from the filesystem after dlopen-ing it is safe, since it will still be mapped in memory.
#else
_unlink(new_so_name);
#endif
//if(0 == rename(new_so_name, "${so_name}")){
// load_so("${so_name}");
//}
lfnmutex_print("reload_so unlocking...");
pthread_mutex_unlock(&live_fn_mutex);
lfnmutex_print("reload_so unlocked");
}
time__sleep_ms(100);
}
}
' )
}
if v.pref.is_so {
cgen.genln(' int load_so(byteptr path) { return 0; }')
}
}

1043
vlib/compiler/main.v Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,262 @@
// 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 compiler
import (
strings
os
)
/*
.vh generation logic.
.vh files contain only function signatures, consts, and types.
They are used together with pre-compiled modules.
*/
// "fn foo(a int) string"
fn (f &Fn) v_definition() string {
//t :=time.ticks()
mut sb := strings.new_builder(100)
if f.is_public {
sb.write('pub ')
}
sb.write('fn ')
if f.is_c {
sb.write('C.')
}
if f.is_method {
recv := f.args[0]
typ := v_type_str(recv.typ).replace('*', '')
mut mu := if recv.is_mut { 'mut' } else { '' }
if recv.ref {
mu = '&'
}
sb.write('($recv.name $mu $typ) ')
}
if f.name.contains('__') {
sb.write(f.name.all_after('__') + '(')
} else {
sb.write('$f.name(')
}
for i, arg in f.args {
if i == 0 && f.is_method { // skip the receiver
continue
}
typ := v_type_str(arg.typ).replace('*', '&')
if arg.name == '' {
sb.write(typ)
} else {
sb.write('$arg.name $typ')
}
if i != f.args.len - 1 {
sb.write(', ')
}
}
sb.write(')')
if f.typ != 'void' {
typ := v_type_str(f.typ).replace('*', '&')
sb.write(' ')
sb.write(typ)
sb.writeln(' ')
}
//println('ms: ${time.ticks() - t}')
return sb.str()
}
fn v_type_str(typ_ string) string {
mut typ := if typ_.ends_with('*') {
'*' + typ_.left(typ_.len - 1)
} else {
typ_
}
typ = typ.replace('Option_', '?')
// fn parent/alias?
if typ.starts_with('fn ') {
mut types := []string
fi_lpar := typ.index_byte(`(`)
li_rpar := typ.last_index_byte(`)`)
ret_type := typ.right(li_rpar+1)
for t in typ.substr(fi_lpar+1, li_rpar).split(',') {
types << v_type_str(t)
}
return 'fn (' + types.join(', ') + ')$ret_type'
}
typ = typ.replace('Option_', '?')
// multiple return
if typ.contains('_V_MulRet') {
words := typ.replace('_V_MulRet_', '').replace('_PTR_', '*').split('_V_')
typ = '('
for i in 0 .. words.len {
typ += v_type_str(words[i])
if i != words.len - 1 {
typ += ','
}
}
typ += ')'
return typ
}
//println('"$typ"')
if typ == '*void' {
return 'voidptr'
}
if typ == '*byte' {
return 'byteptr'
}
if typ.starts_with('array_') {
return '[]' + typ.right(6)
}
if typ.contains('__') {
opt := typ.starts_with('?')
typ = typ.all_after('__')
if opt {
typ = '?' + typ
}
}
return typ
}
fn (v &V) generate_vh() {
println('\n\n\n\nGenerating a V header file for module `$v.mod`')
mod_path := v.mod.replace('.', os.path_separator)
dir := if v.dir.starts_with('vlib') {
'$v_modules_path${os.path_separator}$v.dir'
} else {
'$v_modules_path${os.path_separator}$mod_path'
}
path := dir + '.vh'
pdir := dir.all_before_last(os.path_separator)
if !os.dir_exists(pdir) {
os.mkdir_all(pdir)
// os.mkdir(os.realpath(dir))
}
file := os.create(path) or { panic(err) }
// Consts
mod_def := if v.mod.contains('.') { v.mod.all_after('.') } else { v.mod }
file.writeln('// $v.mod module header \n')
file.writeln('module $mod_def')
file.writeln('// Consts')
if v.table.consts.len > 0 {
file.writeln('const (')
for i, c in v.table.consts {
if c.mod != v.mod {
continue
}
// println('$i $c.name')
//if !c.name.contains('__') {
//continue
//}
name := c.name.all_after('__')
typ := v_type_str(c.typ)
file.writeln('\t$name $typ')
}
file.writeln(')\n')
// Globals
for var in v.table.consts {
if var.mod != v.mod {
continue
}
if !var.is_global {
continue
}
name := var.name.all_after('__')
typ := v_type_str(var.typ)
file.writeln('__global $name $typ')
}
file.writeln('\n')
}
// Types
file.writeln('// Types')
for _, typ in v.table.typesmap {
//println(typ.name)
if typ.mod != v.mod && typ.mod != ''{ // int, string etc mod == ''
// println('skipping type "$typ.name"')
continue
}
if typ.name.contains('_V_MulRet') {
continue
}
mut name := typ.name
if typ.name.contains('__') {
name = typ.name.all_after('__')
}
// type alias
if typ.parent != '' && typ.cat == .alias {
parent := v_type_str(typ.parent)
file.writeln('type $typ.name $parent')
}
if typ.cat in [TypeCategory.struct_, .c_struct] {
c := if typ.is_c { 'C.' } else { '' }
file.writeln('struct ${c}$name {')
// Private fields
for field in typ.fields {
if field.access_mod == .public {
continue
}
field_type := v_type_str(field.typ).replace('*', '&')
file.writeln('\t$field.name $field_type')
}
//file.writeln('pub:')
mut public_str := ''
for field in typ.fields {
if field.access_mod == .private {
continue
}
field_type := v_type_str(field.typ).replace('*', '&')
public_str += '\t$field.name $field_type\n'
//file.writeln('\t$field.name $field_type')
}
if public_str != '' {
file.writeln('pub:' + public_str)
}
file.writeln('}\n')
}
}
// Functions & methods
file.writeln('// Functions')
// Public first
mut fns := []Fn
// TODO fns := v.table.fns.filter(.mod == v.mod)
for _, f in v.table.fns {
if f.mod == v.mod || f.mod == ''{
fns << f
} else {
//println('skipping fn $f.name mod=$f.mod')
}
}
for _, f in fns {
if !f.is_public {
continue
}
file.writeln(f.v_definition())
}
// Private
for _, f in fns {
if f.is_public {
continue
}
file.writeln(f.v_definition())
}
// Methods
file.writeln('\n// Methods //////////////////')
for _, typ in v.table.typesmap {
if typ.mod != v.mod && !(v.mod == 'builtin' && typ.mod == '') {
// println('skipping method typ $typ.name mod=$typ.mod')
continue
}
for method in typ.methods {
file.writeln(method.v_definition())
}
}
file.close()
/*
for i, p in v.parsers {
if v.parsers[i].vh_lines.len > 0 {
os.write_file(p.file_name +'.vh', v.parsers[i].vh_lines.join('\n'))
}
}
*/
}

70
vlib/compiler/modules.v Normal file
View File

@ -0,0 +1,70 @@
// 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 compiler
import os
const (
v_modules_path = os.home_dir() + '.vmodules'
)
// add a module and its deps (module speficic dag method)
pub fn(graph mut DepGraph) from_import_tables(import_tables map[string]FileImportTable) {
for _, fit in import_tables {
mut deps := []string
for _, m in fit.imports {
deps << m
}
graph.add(fit.module_name, deps)
}
}
// get ordered imports (module speficic dag method)
pub fn(graph &DepGraph) imports() []string {
mut mods := []string
for node in graph.nodes {
mods << node.name
}
return mods
}
fn (v &V) module_path(mod string) string {
// submodule support
if mod.contains('.') {
return mod.replace('.', os.path_separator)
// return mod.replace('.', '/')
}
return mod
}
// 'strings' => 'VROOT/vlib/strings'
// 'installed_mod' => '~/.vmodules/installed_mod'
// 'local_mod' => '/path/to/current/dir/local_mod'
fn (v &V) find_module_path(mod string) ?string {
mod_path := v.module_path(mod)
// First check for local modules in the same directory
mut import_path := os.getwd() + '${os.path_separator}$mod_path'
// Now search in vlib/
if mod == 'compiler' || !os.dir_exists(import_path) {
import_path = '$v.lang_dir${os.path_separator}vlib${os.path_separator}$mod_path'
}
//println('ip=$import_path')
// Finally try modules installed with vpm (~/.vmodules)
if !os.dir_exists(import_path) {
import_path = '$v_modules_path${os.path_separator}$mod_path'
if !os.dir_exists(import_path){
return error('module "$mod" not found')
}
}
return import_path
}
[inline] fn mod_gen_name(mod string) string {
return mod.replace('.', '_dot_')
}
[inline] fn mod_gen_name_rev(mod string) string {
return mod.replace('_dot_', '.')
}

466
vlib/compiler/msvc.v Normal file
View File

@ -0,0 +1,466 @@
module compiler
import os
#flag windows -l shell32
// RegOpenKeyExA etc
#flag windows -l advapi32
struct MsvcResult {
full_cl_exe_path string
exe_path string
um_lib_path string
ucrt_lib_path string
vs_lib_path string
um_include_path string
ucrt_include_path string
vs_include_path string
shared_include_path string
}
// Mimics a HKEY
type RegKey voidptr
// Taken from the windows SDK
const (
HKEY_LOCAL_MACHINE = RegKey(0x80000002)
KEY_QUERY_VALUE = (0x0001)
KEY_WOW64_32KEY = (0x0200)
KEY_ENUMERATE_SUB_KEYS = (0x0008)
)
// Given a root key look for one of the subkeys in 'versions' and get the path
fn find_windows_kit_internal(key RegKey, versions []string) ?string {
$if windows {
for version in versions {
required_bytes := 0 // TODO mut
result := C.RegQueryValueExW(key, version.to_wide(), 0, 0, 0, &required_bytes)
length := required_bytes / 2
if result != 0 {
continue
}
alloc_length := (required_bytes + 2)
mut value := &u16(malloc(alloc_length))
if isnil(value) {
continue
}
result2 := C.RegQueryValueExW(key, version.to_wide(), 0, 0, value, &alloc_length)
if result2 != 0 {
continue
}
// We might need to manually null terminate this thing
// So just make sure that we do that
if (value[length - 1] != u16(0)) {
value[length] = u16(0)
}
return string_from_wide(value)
}
}
return error('windows kit not found')
}
struct WindowsKit {
um_lib_path string
ucrt_lib_path string
um_include_path string
ucrt_include_path string
shared_include_path string
}
// Try and find the root key for installed windows kits
fn find_windows_kit_root() ?WindowsKit {
$if windows {
root_key := RegKey(0)
rc := C.RegOpenKeyExA(
HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots', 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &root_key)
defer {C.RegCloseKey(root_key)}
if rc != 0 {
return error('Unable to open root key')
}
// Try and find win10 kit
kit_root := find_windows_kit_internal(root_key, ['KitsRoot10', 'KitsRoot81']) or {
return error('Unable to find a windows kit')
}
kit_lib := kit_root + 'Lib'
// println(kit_lib)
files := os.ls(kit_lib)
mut highest_path := ''
mut highest_int := 0
for f in files {
no_dot := f.replace('.', '')
v_int := no_dot.int()
if v_int > highest_int {
highest_int = v_int
highest_path = f
}
}
kit_lib_highest := kit_lib + '\\$highest_path'
kit_include_highest := kit_lib_highest.replace('Lib', 'Include')
// println('$kit_lib_highest $kit_include_highest')
return WindowsKit {
um_lib_path: kit_lib_highest + '\\um\\x64'
ucrt_lib_path: kit_lib_highest + '\\ucrt\\x64'
um_include_path: kit_include_highest + '\\um'
ucrt_include_path: kit_include_highest + '\\ucrt'
shared_include_path: kit_include_highest + '\\shared'
}
}
return error('Host OS does not support funding a windows kit')
}
struct VsInstallation {
include_path string
lib_path string
exe_path string
}
fn find_vs() ?VsInstallation {
$if !windows {
return error('Host OS does not support finding a Vs installation')
}
// Emily:
// VSWhere is guaranteed to be installed at this location now
// If its not there then end user needs to update their visual studio
// installation!
res := os.exec('""%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath"') or {
return error(err)
}
// println('res: "$res"')
version := os.read_file('$res.output\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt') or {
println('Unable to find msvc version')
return error('Unable to find vs installation')
}
// println('version: $version')
v := if version.ends_with('\n') {
version.left(version.len - 2)
} else {
version
}
lib_path := '$res.output\\VC\\Tools\\MSVC\\$v\\lib\\x64'
include_path := '$res.output\\VC\\Tools\\MSVC\\$v\\include'
if os.file_exists('$lib_path\\vcruntime.lib') {
p := '$res.output\\VC\\Tools\\MSVC\\$v\\bin\\Hostx64\\x64'
// println('$lib_path $include_path')
return VsInstallation{
exe_path: p
lib_path: lib_path
include_path: include_path
}
}
println('Unable to find vs installation (attempted to use lib path "$lib_path")')
return error('Unable to find vs exe folder')
}
fn find_msvc() ?MsvcResult {
$if windows {
wk := find_windows_kit_root() or {
return error('Unable to find windows sdk')
}
vs := find_vs() or {
return error('Unable to find visual studio')
}
return MsvcResult {
full_cl_exe_path: os.realpath( vs.exe_path + os.path_separator + 'cl.exe' )
exe_path: vs.exe_path,
um_lib_path: wk.um_lib_path,
ucrt_lib_path: wk.ucrt_lib_path,
vs_lib_path: vs.lib_path,
um_include_path: wk.um_include_path,
ucrt_include_path: wk.ucrt_include_path,
vs_include_path: vs.include_path,
shared_include_path: wk.shared_include_path,
}
}
$else {
verror('Cannot find msvc on this OS')
return error('msvc not found')
}
}
pub fn (v mut V) cc_msvc() {
r := find_msvc() or {
// TODO: code reuse
if !v.pref.is_keep_c && v.out_name_c != 'v.c' && v.out_name_c != 'v_macos.c' {
os.rm(v.out_name_c)
}
verror('Cannot find MSVC on this OS')
return
}
out_name_obj := os.realpath( v.out_name_c + '.obj' )
// Default arguments
// volatile:ms enables atomic volatile (gcc _Atomic)
// -w: no warnings
// 2 unicode defines
// /Fo sets the object file name - needed so we can clean up after ourselves properly
mut a := ['-w', '/we4013', '/volatile:ms', '/Fo"$out_name_obj"']
if v.pref.is_prod {
a << '/O2'
a << '/MD'
a << '/Zi'
a << '/DNDEBUG'
} else {
a << '/Zi'
a << '/MDd'
}
if v.pref.is_so {
if !v.out_name.ends_with('.dll') {
v.out_name = v.out_name + '.dll'
}
// Build dll
a << '/LD'
} else if !v.out_name.ends_with('.exe') {
v.out_name = v.out_name + '.exe'
}
v.out_name = os.realpath( v.out_name )
//alibs := []string // builtin.o os.o http.o etc
if v.pref.build_mode == .build_module {
}
else if v.pref.build_mode == .default_mode {
/*
b := os.realpath( '$v_modules_path/vlib/builtin.obj' )
alibs << '"$b"'
if !os.file_exists(b) {
println('`builtin.obj` not found')
exit(1)
}
for imp in v.table.imports {
if imp == 'webview' {
continue
}
alibs << '"' + os.realpath( '$v_modules_path/vlib/${imp}.obj' ) + '"'
}
*/
}
if v.pref.sanitize {
println('Sanitize not supported on msvc.')
}
// The C file we are compiling
//a << '"$TmpPath/$v.out_name_c"'
a << '"' + os.realpath( v.out_name_c ) + '"'
// Emily:
// Not all of these are needed (but the compiler should discard them if they are not used)
// these are the defaults used by msbuild and visual studio
mut real_libs := [
'kernel32.lib',
'user32.lib',
'gdi32.lib',
'winspool.lib',
'comdlg32.lib',
'advapi32.lib',
'shell32.lib',
'ole32.lib',
'oleaut32.lib',
'uuid.lib',
'odbc32.lib',
'odbccp32.lib'
]
sflags := v.get_os_cflags().msvc_string_flags()
real_libs << sflags.real_libs
inc_paths := sflags.inc_paths
lib_paths := sflags.lib_paths
other_flags := sflags.other_flags
// Include the base paths
a << '-I "$r.ucrt_include_path"'
a << '-I "$r.vs_include_path"'
a << '-I "$r.um_include_path"'
a << '-I "$r.shared_include_path"'
a << inc_paths
a << other_flags
// Libs are passed to cl.exe which passes them to the linker
a << real_libs.join(' ')
a << '/link'
a << '/NOLOGO'
a << '/OUT:"$v.out_name"'
a << '/LIBPATH:"$r.ucrt_lib_path"'
a << '/LIBPATH:"$r.um_lib_path"'
a << '/LIBPATH:"$r.vs_lib_path"'
a << '/DEBUG:FULL' // required for prod builds to generate PDB
if v.pref.is_prod {
a << '/INCREMENTAL:NO' // Disable incremental linking
a << '/OPT:REF'
a << '/OPT:ICF'
}
a << lib_paths
args := a.join(' ')
cmd := '""$r.full_cl_exe_path" $args"'
// It is hard to see it at first, but the quotes above ARE balanced :-| ...
// Also the double quotes at the start ARE needed.
if v.pref.show_c_cmd || v.pref.is_verbose {
println('\n========== cl cmd line:')
println(cmd)
println('==========\n')
}
// println('$cmd')
res := os.exec(cmd) or {
println(err)
verror('msvc error')
return
}
if res.exit_code != 0 {
verror(res.output)
}
// println(res)
// println('C OUTPUT:')
if !v.pref.is_keep_c && v.out_name_c != 'v.c' && v.out_name_c != 'v_macos.c' {
os.rm(v.out_name_c)
}
// Always remove the object file - it is completely unnecessary
os.rm(out_name_obj)
}
fn build_thirdparty_obj_file_with_msvc(path string, moduleflags []CFlag) {
msvc := find_msvc() or {
println('Could not find visual studio')
return
}
// msvc expects .obj not .o
mut obj_path := '${path}bj'
obj_path = os.realpath(obj_path)
if os.file_exists(obj_path) {
println('$obj_path already build.')
return
}
println('$obj_path not found, building it (with msvc)...')
parent := os.dir(obj_path)
files := os.ls(parent)
mut cfiles := ''
for file in files {
if file.ends_with('.c') {
cfiles += '"' + os.realpath( parent + os.path_separator + file ) + '" '
}
}
include_string := '-I "$msvc.ucrt_include_path" -I "$msvc.vs_include_path" -I "$msvc.um_include_path" -I "$msvc.shared_include_path"'
//println('cfiles: $cfiles')
btarget := moduleflags.c_options_before_target()
atarget := moduleflags.c_options_after_target()
cmd := '""$msvc.full_cl_exe_path" /volatile:ms /Zi /DNDEBUG $include_string /c $btarget $cfiles $atarget /Fo"$obj_path""'
//NB: the quotes above ARE balanced.
println('thirdparty cmd line: $cmd')
res := os.exec(cmd) or {
verror(err)
return
}
println(res.output)
}
struct MsvcStringFlags {
mut:
real_libs []string
inc_paths []string
lib_paths []string
other_flags []string
}
fn (cflags []CFlag) msvc_string_flags() MsvcStringFlags {
mut real_libs := []string
mut inc_paths := []string
mut lib_paths := []string
mut other_flags := []string
for flag in cflags {
//println('fl: $flag.name | flag arg: $flag.value')
// We need to see if the flag contains -l
// -l isnt recognised and these libs will be passed straight to the linker
// by the compiler
if flag.name == '-l' {
if flag.value.ends_with('.dll') {
verror('MSVC cannot link against a dll (`#flag -l $flag.value`)')
}
// MSVC has no method of linking against a .dll
// TODO: we should look for .defs aswell
lib_lib := flag.value + '.lib'
real_libs << lib_lib
}
else if flag.name == '-I' {
inc_paths << flag.format()
}
else if flag.name == '-L' {
lib_paths << flag.value
lib_paths << flag.value + os.path_separator + 'msvc'
// The above allows putting msvc specific .lib files in a subfolder msvc/ ,
// where gcc will NOT find them, but cl will do...
// NB: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough.
// When both a msvc .lib file and .dll file are present in the same folder,
// as for example for glfw3, compilation with gcc would fail.
}
else if flag.value.ends_with('.o') {
// msvc expects .obj not .o
other_flags << '"${flag.value}bj"'
}
else {
other_flags << flag.value
}
}
mut lpaths := []string
for l in lib_paths {
lpaths << '/LIBPATH:"' + os.realpath(l) + '"'
}
return MsvcStringFlags{ real_libs, inc_paths, lpaths, other_flags }
}

View File

@ -0,0 +1,40 @@
module compiler
// `a in [1,2,3]` => `a == 1 || a == 2 || a == 3`
// avoid allocation
// `typ` is the type of `a`
// `ph` is for string_eq()
fn (p mut Parser) in_optimization(typ string, ph int) {
p.check(.lsbr)
mut i := 0
// Get `a` expr value (can be a string literal, not a variable)
expr := p.cgen.cur_line.right(ph)
is_str := typ == 'string'
//println('!! $p.expr_var.name => $name ($typ)')
for p.tok != .rsbr && p.tok != .eof {
if i > 0 {
if is_str {
p.gen(' || string_eq($expr, ')
} else {
p.gen(' || $expr == ')
}
}
if i == 0 {
if is_str {
p.cgen.set_placeholder(ph, ' string_eq(')
p.gen(', ')
} else {
p.gen(' ==')
}
}
p.check_types(p.bool_expression(), typ)
if is_str {
p.gen(')')
}
if p.tok != .rsbr {
p.check(.comma)
}
i++
}
p.check(.rsbr)
}

4160
vlib/compiler/parser.v Normal file

File diff suppressed because it is too large Load Diff

169
vlib/compiler/parser2.v Normal file
View File

@ -0,0 +1,169 @@
module compiler
import strings
fn (p mut Parser) get_type2() Type {
mut mul := false
mut nr_muls := 0
mut typ := ''
mut cat := TypeCategory.struct_
// fn type
if p.tok == .func {
mut f := Fn{name: '_', mod: p.mod}
p.next()
line_nr := p.scanner.line_nr
p.fn_args(mut f)
// Same line, it's a return type
if p.scanner.line_nr == line_nr {
if p.tok == .name {
f.typ = p.get_type()
}
else {
f.typ = 'void'
}
// println('fn return typ=$f.typ')
}
else {
f.typ = 'void'
}
// Register anon fn type
fn_typ := Type {
name: f.typ_str()// 'fn (int, int) string'
mod: p.mod
func: f
cat: TypeCategory.func
}
p.table.register_type2(fn_typ)
return fn_typ
}
// arrays ([]int)
mut is_arr := false
mut is_arr2 := false// [][]int TODO remove this and allow unlimited levels of arrays
is_question := p.tok == .question
if is_question {
p.check(.question)
}
if p.tok == .lsbr {
p.check(.lsbr)
// [10]int
if p.tok == .number {
typ = '[$p.lit]'
p.next()
}
else {
is_arr = true
}
p.check(.rsbr)
// [10][3]int
if p.tok == .lsbr {
p.next()
if p.tok == .number {
typ += '[$p.lit]'
p.check(.number)
}
else {
is_arr2 = true
}
p.check(.rsbr)
}
cat = .array
}
// map[string]int
if !p.builtin_mod && p.tok == .name && p.lit == 'map' {
p.next()
p.check(.lsbr)
key_type := p.check_name()
if key_type != 'string' {
p.error('maps only support string keys for now')
}
p.check(.rsbr)
val_type := p.get_type()// p.check_name()
typ = 'map_$val_type'
p.register_map(typ)
return Type{name: typ}
}
//
for p.tok == .mul {
if p.first_pass() {
p.warn('use `&Foo` instead of `*Foo`')
}
mul = true
nr_muls++
p.check(.mul)
}
if p.tok == .amp {
mul = true
nr_muls++
p.check(.amp)
}
typ += p.lit
if !p.is_struct_init {
// Otherwise we get `foo := FooFoo{` because `Foo` was already
// generated in name_expr()
p.fgen(p.lit)
}
// C.Struct import
if p.lit == 'C' && p.peek() == .dot {
p.next()
p.check(.dot)
typ = p.lit
}
else {
// Module specified? (e.g. gx.Image)
if p.peek() == .dot {
// try resolve full submodule
if !p.builtin_mod && p.import_table.known_alias(typ) {
mod := p.import_table.resolve_alias(typ)
if mod.contains('.') {
typ = mod.replace('.', '_dot_')
}
}
p.next()
p.check(.dot)
typ += '__$p.lit'
}
mut t := p.table.find_type(typ)
// "typ" not found? try "mod__typ"
if t.name == '' && !p.builtin_mod {
// && !p.first_pass() {
if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') &&
!typ.starts_with('[') {
typ = p.prepend_mod(typ)
}
t = p.table.find_type(typ)
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 {
// println(q.name)
// }
p.error('unknown type `$typ`')
}
}
}
if typ == 'void' {
p.error('unknown type `$typ`')
}
if mul {
typ += strings.repeat(`*`, nr_muls)
}
// Register an []array type
if is_arr2 {
typ = 'array_array_$typ'
p.register_array(typ)
}
else if is_arr {
typ = 'array_$typ'
// We come across "[]User" etc ?
p.register_array(typ)
}
p.next()
if is_question {
typ = 'Option_$typ'
p.table.register_type_with_parent(typ, 'Option')
}
if typ.last_index('__') > typ.index('__') {
p.error('2 __ in gettype(): typ="$typ"')
}
return Type{name: typ, cat: cat}
}

242
vlib/compiler/query.v Normal file
View File

@ -0,0 +1,242 @@
// 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 compiler
import strings
fn sql_params2params_gen(sql_params []string, sql_types []string, qprefix string) string {
mut params_gen := ''
for i, mparam in sql_params {
param := mparam.trim_space()
paramtype := sql_types[ i ]
if param[0].is_digit() {
params_gen += '${qprefix}params[$i] = int_str($param).str;\n'
}else if param[0] == `\'` {
sparam := param.trim('\'')
params_gen += '${qprefix}params[$i] = "$sparam";\n'
} else {
// A variable like q.nr_orders
if paramtype == 'int' {
params_gen += '${qprefix}params[$i] = int_str( $param ).str;\n'
}else if paramtype == 'string' {
params_gen += '${qprefix}params[$i] = ${param}.str;\n'
}else{
verror('orm: only int and string variable types are supported in queries')
}
}
}
//println('>>>>>>>> params_gen')
//println( params_gen )
return params_gen
}
// `db.select from User where id == 1 && nr_bookings > 0`
fn (p mut Parser) select_query(fn_ph int) string {
// NB: qprefix and { p.sql_i, p.sql_params, p.sql_types } SHOULD be reset for each query,
// because we can have many queries in the _same_ scope.
qprefix := p.get_tmp().replace('tmp','sql') + '_'
p.sql_i = 0
p.sql_params = []string
p.sql_types = []string
mut q := 'select '
p.check(.key_select)
n := p.check_name()
if n == 'count' {
q += 'count(*) from '
p.check_name()
}
table_name := p.check_name()
// Register this type's fields as variables so they can be used in where expressions
typ := p.table.find_type(table_name)
if typ.name == '' {
p.error('unknown type `$table_name`')
}
//fields := typ.fields.filter(typ == 'string' || typ == 'int')
// get only string and int fields
mut fields := []Var
for i, field in typ.fields {
if field.typ != 'string' && field.typ != 'int' {
continue
}
fields << field
}
if fields.len == 0 {
p.error('V orm: select: empty fields in `$table_name`')
}
if fields[0].name != 'id' {
p.error('V orm: `id int` must be the first field in `$table_name`')
}
// 'select id, name, age from...'
if n == 'from' {
for i, field in fields {
q += field.name
if i < fields.len - 1 {
q += ', '
}
}
q += ' from '
}
for field in fields {
//println('registering sql field var $field.name')
if field.typ != 'string' && field.typ != 'int' {
continue
}
p.register_var({ field | is_used:true })
}
q += table_name
// `where` statement
if p.tok == .name && p.lit == 'where' {
p.next()
p.cgen.start_tmp()
p.is_sql = true
p.bool_expression()
p.is_sql = false
q += ' where ' + p.cgen.end_tmp()
}
// limit?
mut query_one := false
if p.tok == .name && p.lit == 'limit' {
p.next()
p.cgen.start_tmp()
p.is_sql = true
p.bool_expression()
p.is_sql = false
limit := p.cgen.end_tmp()
q += ' limit ' + limit
// `limit 1` means we are getting `?User`, not `[]User`
if limit.trim_space() == '1' {
query_one = true
}
}
println('sql query="$q"')
p.cgen.insert_before('// DEBUG_SQL prefix: $qprefix | fn_ph: $fn_ph | query: "$q" ')
if n == 'count' {
p.cgen.set_placeholder(fn_ph, 'pg__DB_q_int(')
p.gen(', tos2("$q"))')
} else {
// Build an object, assign each field.
tmp := p.get_tmp()
mut obj_gen := strings.new_builder(300)
for i, field in fields {
mut cast := ''
if field.typ == 'int' {
cast = 'v_string_int'
}
obj_gen.writeln('${qprefix}$tmp . $field.name = $cast( *(string*)array_get(${qprefix}row.vals, $i) );')
}
// One object
if query_one {
mut params_gen := sql_params2params_gen( p.sql_params, p.sql_types, qprefix )
p.cgen.insert_before('
char* ${qprefix}params[$p.sql_i];
$params_gen
Option_${table_name} opt_${qprefix}$tmp;
void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ;
array_pg__Row ${qprefix}rows = pg__res_to_rows ( ${qprefix}res ) ;
Option_pg__Row opt_${qprefix}row = pg__rows_first_or_empty( ${qprefix}rows );
if (! opt_${qprefix}row . ok ) {
opt_${qprefix}$tmp = v_error( opt_${qprefix}row . error );
}else{
$table_name ${qprefix}$tmp;
pg__Row ${qprefix}row = *(pg__Row*) opt_${qprefix}row . data;
${obj_gen.str()}
opt_${qprefix}$tmp = opt_ok( & ${qprefix}$tmp, sizeof($table_name) );
}
')
p.cgen.resetln('opt_${qprefix}$tmp')
}
// Array
else {
q += ' order by id'
params_gen := sql_params2params_gen( p.sql_params, p.sql_types, qprefix )
p.cgen.insert_before('char* ${qprefix}params[$p.sql_i];
$params_gen
void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ;
array_pg__Row ${qprefix}rows = pg__res_to_rows(${qprefix}res);
// TODO preallocate
array ${qprefix}arr_$tmp = new_array(0, 0, sizeof($table_name));
for (int i = 0; i < ${qprefix}rows.len; i++) {
pg__Row ${qprefix}row = *(pg__Row*)array_get(${qprefix}rows, i);
$table_name ${qprefix}$tmp;
${obj_gen.str()}
_PUSH(&${qprefix}arr_$tmp, ${qprefix}$tmp, ${tmp}2, $table_name);
}
')
p.cgen.resetln('${qprefix}arr_$tmp')
}
}
if n == 'count' {
return 'int'
} else if query_one {
opt_type := 'Option_$table_name'
p.cgen.typedefs << 'typedef Option $opt_type;'
p.table.register_type( opt_type )
return opt_type
} else {
p.register_array('array_$table_name')
return 'array_$table_name'
}
}
// `db.insert(user)`
fn (p mut Parser) insert_query(fn_ph int) {
p.check_name()
p.check(.lpar)
var_name := p.check_name()
p.check(.rpar)
var := p.find_var(var_name) or { return }
typ := p.table.find_type(var.typ)
mut fields := []Var
for i, field in typ.fields {
if field.typ != 'string' && field.typ != 'int' {
continue
}
fields << field
}
if fields.len == 0 {
p.error('V orm: insert: empty fields in `$var.typ`')
}
if fields[0].name != 'id' {
p.error('V orm: `id int` must be the first field in `$var.typ`')
}
table_name := var.typ
mut sfields := '' // 'name, city, country'
mut params := '' // params[0] = 'bob'; params[1] = 'Vienna';
mut vals := '' // $1, $2, $3...
mut nr_vals := 0
for i, field in fields {
if field.name == 'id' {
continue
}
sfields += field.name
vals += '$' + i.str()
nr_vals++
params += 'params[${i-1}] = '
if field.typ == 'string' {
params += '$var_name . $field.name .str;\n'
} else if field.typ == 'int' {
params += 'int_str($var_name . $field.name).str;\n'
} else {
p.error('V ORM: unsupported type `$field.typ`')
}
if i < fields.len - 1 {
sfields += ', '
vals += ', '
}
}
p.cgen.insert_before('char* params[$nr_vals];' + params)
p.cgen.set_placeholder(fn_ph, 'PQexecParams( ')
p.genln('.conn, "insert into $table_name ($sfields) values ($vals)", $nr_vals,
0, params, 0, 0, 0)')
}

180
vlib/compiler/repl.v Normal file
View File

@ -0,0 +1,180 @@
// 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 compiler
import os
import term
struct Repl {
mut:
indent int
in_func bool
line string
lines []string
temp_lines []string
functions_name []string
functions []string
}
fn (r mut Repl) checks() bool {
mut in_string := false
mut is_cut := false
was_indent := r.indent > 0
for i := 0; i < r.line.len; i++ {
if r.line[i] == `\'` && (i == 0 || r.line[i - 1] != `\\`) {
in_string = !in_string
}
if r.line[i] == `{` && !in_string {
r.line = r.line.left(i + 1) + '\n' + r.line.right(i + 1)
is_cut = true
i++
r.indent++
}
if r.line[i] == `}` && !in_string {
r.line = r.line.left(i) + '\n' + r.line.right(i)
is_cut = true
i++
r.indent--
if r.indent == 0 {
r.in_func = false
}
}
if i + 2 < r.line.len && r.indent == 0 && r.line[i + 1] == `f` && r.line[i + 2] == `n` {
r.in_func = true
}
}
return r.in_func || (was_indent && r.indent <= 0) || r.indent > 0 || is_cut
}
fn (r &Repl) function_call(line string) bool {
for function in r.functions_name {
if line.starts_with(function) {
return true
}
}
return false
}
pub fn repl_help() {
version_hash := vhash()
println('
V $Version $version_hash
help Displays this information.
Ctrl-C, Ctrl-D, exit Exits the REPL.
clear Clears the screen.
')
}
pub fn run_repl() []string {
version_hash := vhash()
println('V $Version $version_hash')
println('Use Ctrl-C or `exit` to exit')
file := '.vrepl.v'
temp_file := '.vrepl_temp.v'
defer {
os.rm(file)
os.rm(temp_file)
os.rm(file.left(file.len - 2))
os.rm(temp_file.left(temp_file.len - 2))
}
mut r := Repl{}
vexe := os.args[0]
for {
if r.indent == 0 {
print('>>> ')
}
else {
print('... ')
}
r.line = os.get_raw_line()
if r.line.trim_space() == '' && r.line.ends_with('\n') {
continue
}
r.line = r.line.trim_space()
if r.line.len == -1 || r.line == '' || r.line == 'exit' {
break
}
if r.line == '\n' {
continue
}
if r.line == 'clear' {
term.erase_display('2')
continue
}
if r.line == 'help' {
repl_help()
continue
}
if r.line.starts_with('fn') {
r.in_func = true
r.functions_name << r.line.all_after('fn').all_before('(').trim_space()
}
was_func := r.in_func
if r.checks() {
for line in r.line.split('\n') {
if r.in_func || was_func {
r.functions << line
}
else {
r.temp_lines << line
}
}
if r.indent > 0 {
continue
}
r.line = ''
}
// Save the source only if the user is printing something,
// but don't add this print call to the `lines` array,
// so that it doesn't get called during the next print.
if r.line.starts_with('print') {
source_code := r.functions.join('\n') + r.lines.join('\n') + '\n' + r.line
os.write_file(file, source_code)
s := os.exec('$vexe run $file -repl') or {
verror(err)
return []string
}
vals := s.output.split('\n')
for i:=0; i < vals.len; i++ {
println(vals[i])
}
}
else {
mut temp_line := r.line
mut temp_flag := false
func_call := r.function_call(r.line)
if !(r.line.contains(' ') || r.line.contains(':') || r.line.contains('=') || r.line.contains(',') || r.line == '') && !func_call {
temp_line = 'println($r.line)'
temp_flag = true
}
temp_source_code := r.functions.join('\n') + r.lines.join('\n') + '\n' + r.temp_lines.join('\n') + '\n' + temp_line
os.write_file(temp_file, temp_source_code)
s := os.exec('$vexe run $temp_file -repl') or {
verror(err)
return []string
}
if !func_call && s.exit_code == 0 && !temp_flag {
for r.temp_lines.len > 0 {
if !r.temp_lines[0].starts_with('print') {
r.lines << r.temp_lines[0]
}
r.temp_lines.delete(0)
}
r.lines << r.line
}
else {
for r.temp_lines.len > 0 {
r.temp_lines.delete(0)
}
}
vals := s.output.split('\n')
for i:=0; i<vals.len; i++ {
println(vals[i])
}
}
}
return r.lines
}

808
vlib/compiler/scanner.v Normal file
View File

@ -0,0 +1,808 @@
// 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 compiler
import (
os
strings
)
const (
single_quote = `\'`
double_quote = `"`
error_context_before = 2 // how many lines of source context to print before the pointer line
error_context_after = 2 // ^^^ same, but after
)
struct Scanner {
mut:
file_path string
text string
pos int
line_nr int
last_nl_pos int // for calculating column
inside_string bool
inter_start bool // for hacky string interpolation TODO simplify
inter_end bool
debug bool
line_comment string
started bool
// vfmt fields
fmt_out strings.Builder
fmt_indent int
fmt_line_empty bool
prev_tok TokenKind
fn_name string // needed for @FN
should_print_line_on_error bool
should_print_errors_in_color bool
should_print_relative_paths_on_error bool
quote byte // which quote is used to denote current string: ' or "
file_lines []string // filled *only on error* by rescanning the source till the error (and several lines more)
}
// new scanner from file.
fn new_scanner_file(file_path string) &Scanner {
if !os.file_exists(file_path) {
verror("$file_path doesn't exist")
}
mut raw_text := os.read_file(file_path) or {
verror('scanner: failed to open $file_path')
return 0
}
// BOM check
if raw_text.len >= 3 {
c_text := raw_text.str
if c_text[0] == 0xEF && c_text[1] == 0xBB && c_text[2] == 0xBF {
// skip three BOM bytes
offset_from_begin := 3
raw_text = tos(c_text[offset_from_begin], vstrlen(c_text) - offset_from_begin)
}
}
mut s := new_scanner(raw_text)
s.file_path = file_path
return s
}
// new scanner from string.
fn new_scanner(text string) &Scanner {
return &Scanner {
text: text
fmt_out: strings.new_builder(1000)
should_print_line_on_error: true
should_print_errors_in_color: true
should_print_relative_paths_on_error: true
}
}
// TODO remove once multiple return values are implemented
struct ScanRes {
tok TokenKind
lit string
}
fn scan_res(tok TokenKind, lit string) ScanRes {
return ScanRes{tok, lit}
}
fn (s mut Scanner) ident_name() string {
start := s.pos
for {
s.pos++
if s.pos >= s.text.len {
break
}
c := s.text[s.pos]
if !is_name_char(c) && !c.is_digit() {
break
}
}
name := s.text.substr(start, s.pos)
s.pos--
return name
}
fn (s mut Scanner) ident_hex_number() string {
start_pos := s.pos
s.pos += 2 // skip '0x'
for {
if s.pos >= s.text.len {
break
}
c := s.text[s.pos]
if !c.is_hex_digit() {
break
}
s.pos++
}
number := s.text.substr(start_pos, s.pos)
s.pos--
return number
}
fn (s mut Scanner) ident_oct_number() string {
start_pos := s.pos
for {
if s.pos >= s.text.len {
break
}
c := s.text[s.pos]
if c.is_digit() {
if !c.is_oct_digit() {
s.error('malformed octal constant')
}
} else {
break
}
s.pos++
}
number := s.text.substr(start_pos, s.pos)
s.pos--
return number
}
fn (s mut Scanner) ident_dec_number() string {
start_pos := s.pos
// scan integer part
for s.pos < s.text.len && s.text[s.pos].is_digit() {
s.pos++
}
// e.g. 1..9
// we just return '1' and don't scan '..9'
if s.expect('..', s.pos) {
number := s.text.substr(start_pos, s.pos)
s.pos--
return number
}
// scan fractional part
if s.pos < s.text.len && s.text[s.pos] == `.` {
s.pos++
for s.pos < s.text.len && s.text[s.pos].is_digit() {
s.pos++
}
if !s.inside_string && s.pos < s.text.len && s.text[s.pos] == `f` {
s.error('no `f` is needed for floats')
}
}
// scan exponential part
mut has_exponential_part := false
if s.expect('e+', s.pos) || s.expect('e-', s.pos) {
exp_start_pos := s.pos += 2
for s.pos < s.text.len && s.text[s.pos].is_digit() {
s.pos++
}
if exp_start_pos == s.pos {
s.error('exponent has no digits')
}
has_exponential_part = true
}
// error check: 1.23.4, 123.e+3.4
if s.pos < s.text.len && s.text[s.pos] == `.` {
if has_exponential_part {
s.error('exponential part should be integer')
}
else {
s.error('too many decimal points in number')
}
}
number := s.text.substr(start_pos, s.pos)
s.pos--
return number
}
fn (s mut Scanner) ident_number() string {
if s.expect('0x', s.pos) {
return s.ident_hex_number()
}
if s.expect('0.', s.pos) || s.expect('0e', s.pos) {
return s.ident_dec_number()
}
if s.text[s.pos] == `0` {
return s.ident_oct_number()
}
return s.ident_dec_number()
}
fn (s mut Scanner) skip_whitespace() {
for s.pos < s.text.len && s.text[s.pos].is_white() {
// Count \r\n as one line
if is_nl(s.text[s.pos]) && !s.expect('\r\n', s.pos-1) {
s.inc_line_number()
}
s.pos++
}
}
fn (s mut Scanner) scan() ScanRes {
if s.line_comment != '' {
//s.fgenln('// LOL "$s.line_comment"')
//s.line_comment = ''
}
if s.started {
s.pos++
}
s.started = true
if s.pos >= s.text.len {
return scan_res(.eof, '')
}
// skip whitespace
if !s.inside_string {
s.skip_whitespace()
}
// End of $var, start next string
if s.inter_end {
if s.text[s.pos] == s.quote { //single_quote {
s.inter_end = false
return scan_res(.str, '')
}
s.inter_end = false
return scan_res(.str, s.ident_string())
}
s.skip_whitespace()
// end of file
if s.pos >= s.text.len {
return scan_res(.eof, '')
}
// handle each char
c := s.text[s.pos]
mut nextc := `\0`
if s.pos + 1 < s.text.len {
nextc = s.text[s.pos + 1]
}
// name or keyword
if is_name_char(c) {
name := s.ident_name()
// tmp hack to detect . in ${}
// Check if not .eof to prevent panic
next_char := if s.pos + 1 < s.text.len { s.text[s.pos + 1] } else { `\0` }
if is_key(name) {
return scan_res(key_to_token(name), '')
}
// 'asdf $b' => "b" is the last name in the string, dont start parsing string
// at the next ', skip it
if s.inside_string {
if next_char == s.quote {
s.inter_end = true
s.inter_start = false
s.inside_string = false
}
}
if s.inter_start && next_char != `.` {
s.inter_end = true
s.inter_start = false
}
if s.pos == 0 && next_char == ` ` {
s.pos++
//If a single letter name at the start of the file, increment
//Otherwise the scanner would be stuck at s.pos = 0
}
return scan_res(.name, name)
}
// `123`, `.123`
else if c.is_digit() || (c == `.` && nextc.is_digit()) {
num := s.ident_number()
return scan_res(.number, num)
}
// all other tokens
switch c {
case `+`:
if nextc == `+` {
s.pos++
return scan_res(.inc, '')
}
else if nextc == `=` {
s.pos++
return scan_res(.plus_assign, '')
}
return scan_res(.plus, '')
case `-`:
if nextc == `-` {
s.pos++
return scan_res(.dec, '')
}
else if nextc == `=` {
s.pos++
return scan_res(.minus_assign, '')
}
return scan_res(.minus, '')
case `*`:
if nextc == `=` {
s.pos++
return scan_res(.mult_assign, '')
}
return scan_res(.mul, '')
case `^`:
if nextc == `=` {
s.pos++
return scan_res(.xor_assign, '')
}
return scan_res(.xor, '')
case `%`:
if nextc == `=` {
s.pos++
return scan_res(.mod_assign, '')
}
return scan_res(.mod, '')
case `?`:
return scan_res(.question, '')
case single_quote, double_quote:
return scan_res(.str, s.ident_string())
case `\``: // ` // apostrophe balance comment. do not remove
return scan_res(.chartoken, s.ident_char())
case `(`:
return scan_res(.lpar, '')
case `)`:
return scan_res(.rpar, '')
case `[`:
return scan_res(.lsbr, '')
case `]`:
return scan_res(.rsbr, '')
case `{`:
// Skip { in ${ in strings
if s.inside_string {
return s.scan()
}
return scan_res(.lcbr, '')
case `$`:
return scan_res(.dollar, '')
case `}`:
// s = `hello $name !`
// s = `hello ${name} !`
if s.inside_string {
s.pos++
// TODO UNNEEDED?
if s.text[s.pos] == single_quote {
s.inside_string = false
return scan_res(.str, '')
}
return scan_res(.str, s.ident_string())
}
else {
return scan_res(.rcbr, '')
}
case `&`:
if nextc == `=` {
s.pos++
return scan_res(.and_assign, '')
}
if nextc == `&` {
s.pos++
return scan_res(.and, '')
}
return scan_res(.amp, '')
case `|`:
if nextc == `|` {
s.pos++
return scan_res(.logical_or, '')
}
if nextc == `=` {
s.pos++
return scan_res(.or_assign, '')
}
return scan_res(.pipe, '')
case `,`:
return scan_res(.comma, '')
case `@`:
s.pos++
name := s.ident_name()
// @FN => will be substituted with the name of the current V function
// @FILE => will be substituted with the path of the V source file
// @LINE => will be substituted with the V line number where it appears (as a string).
// @COLUMN => will be substituted with the column where it appears (as a string).
// @VHASH => will be substituted with the shortened commit hash of the V compiler (as a string).
// This allows things like this:
// println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @FN)
// ... which is useful while debugging/tracing
if name == 'FN' { return scan_res(.str, s.fn_name) }
if name == 'FILE' { return scan_res(.str, cescaped_path(os.realpath(s.file_path))) }
if name == 'LINE' { return scan_res(.str, (s.line_nr+1).str()) }
if name == 'COLUMN' { return scan_res(.str, (s.current_column()).str()) }
if name == 'VHASH' { return scan_res(.str, vhash()) }
if !is_key(name) {
s.error('@ must be used before keywords (e.g. `@type string`)')
}
return scan_res(.name, name)
case `\r`:
if nextc == `\n` {
s.pos++
s.last_nl_pos = s.pos
return scan_res(.nl, '')
}
case `\n`:
s.last_nl_pos = s.pos
return scan_res(.nl, '')
case `.`:
if nextc == `.` {
s.pos++
if s.text[s.pos+1] == `.` {
s.pos++
return scan_res(.ellipsis, '')
}
return scan_res(.dotdot, '')
}
return scan_res(.dot, '')
case `#`:
start := s.pos + 1
s.ignore_line()
if nextc == `!` {
// treat shebang line (#!) as a comment
s.line_comment = s.text.substr(start + 1, s.pos).trim_space()
s.fgenln('// shebang line "$s.line_comment"')
return s.scan()
}
hash := s.text.substr(start, s.pos)
return scan_res(.hash, hash.trim_space())
case `>`:
if nextc == `=` {
s.pos++
return scan_res(.ge, '')
}
else if nextc == `>` {
if s.pos + 2 < s.text.len && s.text[s.pos + 2] == `=` {
s.pos += 2
return scan_res(.righ_shift_assign, '')
}
s.pos++
return scan_res(.righ_shift, '')
}
else {
return scan_res(.gt, '')
}
case 0xE2:
//case `≠`:
if nextc == 0x89 && s.text[s.pos + 2] == 0xA0 {
s.pos += 2
return scan_res(.ne, '')
}
// ⩽
else if nextc == 0x89 && s.text[s.pos + 2] == 0xBD {
s.pos += 2
return scan_res(.le, '')
}
// ⩾
else if nextc == 0xA9 && s.text[s.pos + 2] == 0xBE {
s.pos += 2
return scan_res(.ge, '')
}
case `<`:
if nextc == `=` {
s.pos++
return scan_res(.le, '')
}
else if nextc == `<` {
if s.pos + 2 < s.text.len && s.text[s.pos + 2] == `=` {
s.pos += 2
return scan_res(.left_shift_assign, '')
}
s.pos++
return scan_res(.left_shift, '')
}
else {
return scan_res(.lt, '')
}
case `=`:
if nextc == `=` {
s.pos++
return scan_res(.eq, '')
}
else if nextc == `>` {
s.pos++
return scan_res(.arrow, '')
}
else {
return scan_res(.assign, '')
}
case `:`:
if nextc == `=` {
s.pos++
return scan_res(.decl_assign, '')
}
else {
return scan_res(.colon, '')
}
case `;`:
return scan_res(.semicolon, '')
case `!`:
if nextc == `=` {
s.pos++
return scan_res(.ne, '')
}
else {
return scan_res(.not, '')
}
case `~`:
return scan_res(.bit_not, '')
case `/`:
if nextc == `=` {
s.pos++
return scan_res(.div_assign, '')
}
if nextc == `/` {
start := s.pos + 1
s.ignore_line()
s.line_comment = s.text.substr(start + 1, s.pos)
s.line_comment = s.line_comment.trim_space()
s.fgenln('// ${s.prev_tok.str()} "$s.line_comment"')
// Skip the comment (return the next token)
return s.scan()
}
// Multiline comments
if nextc == `*` {
start := s.pos
mut nest_count := 1
// Skip comment
for nest_count > 0 {
s.pos++
if s.pos >= s.text.len {
s.line_nr--
s.error('comment not terminated')
}
if s.text[s.pos] == `\n` {
s.inc_line_number()
continue
}
if s.expect('/*', s.pos) {
nest_count++
continue
}
if s.expect('*/', s.pos) {
nest_count--
}
}
s.pos++
end := s.pos + 1
comm := s.text.substr(start, end)
s.fgenln(comm)
// Skip if not in fmt mode
return s.scan()
}
return scan_res(.div, '')
}
$if windows {
if c == `\0` {
return scan_res(.eof, '')
}
}
mut msg := 'invalid character `${c.str()}`'
if c == `"` {
msg += ', use \' to denote strings'
}
s.error(msg)
return scan_res(.eof, '')
}
fn (s &Scanner) current_column() int {
return s.pos - s.last_nl_pos
}
fn (s Scanner) count_symbol_before(p int, sym byte) int {
mut count := 0
for i:=p; i>=0; i-- {
if s.text[i] != sym {
break
}
count++
}
return count
}
// println('array out of bounds $idx len=$a.len')
// This is really bad. It needs a major clean up
fn (s mut Scanner) ident_string() string {
q := s.text[s.pos]
if (q == single_quote || q == double_quote) && !s.inside_string{
s.quote = q
}
//if s.file_path.contains('string_test') {
//println('\nident_string() at char=${s.text[s.pos].str()}')
//println('linenr=$s.line_nr quote= $qquote ${qquote.str()}')
//}
mut start := s.pos
s.inside_string = false
slash := `\\`
for {
s.pos++
if s.pos >= s.text.len {
break
}
c := s.text[s.pos]
prevc := s.text[s.pos - 1]
// end of string
if c == s.quote && (prevc != slash || (prevc == slash && s.text[s.pos - 2] == slash)) {
// handle '123\\' slash at the end
break
}
if c == `\n` {
s.inc_line_number()
}
// Don't allow \0
if c == `0` && s.pos > 2 && s.text[s.pos - 1] == `\\` {
s.error('0 character in a string literal')
}
// Don't allow \x00
if c == `0` && s.pos > 5 && s.expect('\\x0', s.pos - 3) {
s.error('0 character in a string literal')
}
// ${var}
if c == `{` && prevc == `$` && s.count_symbol_before(s.pos-2, `\\`) % 2 == 0 {
s.inside_string = true
// so that s.pos points to $ at the next step
s.pos -= 2
break
}
// $var
if (c.is_letter() || c == `_`) && prevc == `$` && s.count_symbol_before(s.pos-2, `\\`) % 2 == 0 {
s.inside_string = true
s.inter_start = true
s.pos -= 2
break
}
}
mut lit := ''
if s.text[start] == s.quote {
start++
}
mut end := s.pos
if s.inside_string {
end++
}
if start > s.pos{}
else {
lit = s.text.substr(start, end)
}
return lit
}
fn (s mut Scanner) ident_char() string {
start := s.pos
slash := `\\`
mut len := 0
for {
s.pos++
if s.pos >= s.text.len {
break
}
if s.text[s.pos] != slash {
len++
}
double_slash := s.expect('\\\\', s.pos - 2)
if s.text[s.pos] == `\`` && (s.text[s.pos - 1] != slash || double_slash) { // ` // apostrophe balance comment. do not remove
if double_slash {
len++
}
break
}
}
len--
c := s.text.substr(start + 1, s.pos)
if len != 1 {
u := c.ustring()
if u.len != 1 {
s.error('invalid character literal (more than one character: $len)')
}
}
if c == '\\`' {
return '`'
}
// Escapes a `'` character
return if c == '\'' { '\\' + c } else { c }
}
fn (s &Scanner) expect(want string, start_pos int) bool {
end_pos := start_pos + want.len
if start_pos < 0 || start_pos >= s.text.len {
return false
}
if end_pos < 0 || end_pos > s.text.len {
return false
}
for pos in start_pos..end_pos {
if s.text[pos] != want[pos-start_pos] {
return false
}
}
return true
}
fn (s mut Scanner) debug_tokens() {
s.pos = 0
s.started = false
s.debug = true
fname := s.file_path.all_after(os.path_separator)
println('\n===DEBUG TOKENS $fname===')
for {
res := s.scan()
tok := res.tok
lit := res.lit
print(tok.str())
if lit != '' {
println(' `$lit`')
}
else {
println('')
}
if tok == .eof {
println('============ END OF DEBUG TOKENS ==================')
break
}
}
}
fn (s mut Scanner) ignore_line() {
s.eat_to_end_of_line()
s.inc_line_number()
}
fn (s mut Scanner) eat_to_end_of_line(){
for s.pos < s.text.len && s.text[s.pos] != `\n` {
s.pos++
}
}
fn (s mut Scanner) inc_line_number() {
s.last_nl_pos = s.pos
s.line_nr++
}
fn is_name_char(c byte) bool {
return c.is_letter() || c == `_`
}
fn is_nl(c byte) bool {
return c == `\r` || c == `\n`
}
fn contains_capital(s string) bool {
for c in s {
if c >= `A` && c <= `Z` {
return true
}
}
return false
}
// HTTPRequest bad
// HttpRequest good
fn good_type_name(s string) bool {
if s.len < 4 {
return true
}
for i in 2 .. s.len {
if s[i].is_capital() && s[i-1].is_capital() && s[i-2].is_capital() {
return false
}
}
return true
}
// registration_date good
// registrationdate bad
fn (s &Scanner) validate_var_name(name string) {
if name.len > 11 && !name.contains('_') {
s.error('bad variable name `$name`\n' +
'looks like you have a multi-word name without separating them with `_`' +
'\nfor example, use `registration_date` instead of `registrationdate` ')
}
}

1035
vlib/compiler/table.v Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
#include <stdio.h>
int increment_val(int n) {
return n + 2;
}
// ~26% faster
void increment_ptr(int* n) {
*n += 2;
}
int main() {
int n = 0;
for (int i = 0; i < 1000000000; i++) {
n = increment_val(n);
//increment_ptr(&n);
}
printf("%d\n", n);
return 0;
}

View File

@ -0,0 +1,51 @@
fn foo() string {
println('foo()')
return 'foo'
}
fn foo2() string {
println('start')
defer { println('defer') }
defer { println('defer2') }
println('end')
return foo()
}
fn test_defer() {
assert foo2() == 'foo'
}
fn set_num(i int, n mut Num) {
defer { n.val+=1 }
println("Hi")
if i < 5 {
return
} else {
n.val+=1
}
}
fn set_num_opt(n mut Num) ?int {
defer { n.val = 1 }
return 99
}
struct Num {
mut:
val int
}
fn test_defer_early_exit() {
mut sum := Num{0}
for i in 0..10 {
set_num(i, mut sum)
}
println("sum: $sum.val")
assert sum.val == 15
}
fn test_defer_option() {
mut ok := Num{0}
set_num_opt(mut ok)
assert ok.val == 1
}

View File

@ -0,0 +1,24 @@
enum Color {
red
blue
green
}
fn test_enum() {
assert Color.red == Color.red
assert Color.blue == Color.blue
assert Color.green == Color.green
assert Color.red == .red
assert Color.blue == .blue
assert Color.green == .green
assert Color.red != Color.blue
assert Color.red != Color.green
assert Color.blue != Color.green
mut color := Color.red
assert color == Color.red
color = .green
assert color == Color.green
}

View File

@ -0,0 +1,32 @@
fn test_fixed_array_can_be_assigned(){
x := 2.32
mut v := [8]f32
v = [1.0, x, 3.0,4.0,5.0,6.0,7.0,8.0]!!
assert v[1] == x
}
fn test_fixed_array_can_be_used_in_declaration(){
x := 2.32
v := [1.0, x, 3.0,4.0,5.0,6.0,7.0,8.0]!!
assert v[1] == x
}
struct Context {
pub mut:
vb [8]f32
}
fn test_fixed_array_can_be_assigned_to_a_struct_field(){
mut ctx := Context{}
x := 2.32
ctx.vb = [1.1, x, 3.3, 4.4, 5.0, 6.0, 7.0, 8.9]!!
assert ctx.vb[1] == x
assert ctx.vb[7] == 8.9
/*
println( ctx.vb[0] )
println( ctx.vb[1] )
println( ctx.vb[2] )
println( ctx.vb[3] )
*/
}

View File

@ -0,0 +1,19 @@
struct UserData {
test string
}
fn test_fn_multiple_returns() {
name, age, groups, data := fn_mr_get_user()
assert name == 'joe'
assert age == 34
assert groups[0] == 'admins'
assert groups[1] == 'users'
assert data.test == 'Test Data'
println('name: $name | age: $age | groups: ' + groups.join(',') + ' | data: $data.test')
}
fn fn_mr_get_user() (string, int, []string, UserData) {
groups := ['admins', 'users']
data := UserData{test: 'Test Data'}
return 'joe',34,groups,data
}

View File

@ -0,0 +1,121 @@
// 1 line comment
/* 1 line comment */
/*
multi line comment (1)
multi line comment (2)
multi line comment (3)
*/
/*
multi line comment (1)
/*
nested comment
*/
/*nested comment*/
/*nested comment
*/
/* nested comment */
/* /* nested comment */ */
multi line comment (2)
*/
type myfn fn (int) string
type myfn2 fn (a int, b int) int
type myfn3 fn (int, int)
fn myfn4(string)
fn foobar()
fn slopediv(num u32, den u32) int
type f1 fn ()
type f2 fn (voidptr)
type f3 fn (voidptr, voidptr)
type f4 fn (voidptr) int
type f5 fn (int, int) int
type f6 fn (int, int)
fn C.atoi(byteptr) int
fn foo() {
}
type actionf_v fn ()
type actionf_p1 fn (voidptr)
type actionf_p2 fn (voidptr, voidptr)
// TODO
fn modify_array(a mut []int) {
a[0] = 10
for i in 0..a.len {
a[i] = a[i] * 2
}
//a << 888
}
fn test_mut_array() {
mut nums := [1, 2, 3]
modify_array(mut nums)
//assert nums.len == 4
// println(nums)
assert nums[0] == 20
assert nums[1] == 4
assert nums[2] == 6
//assert nums[3] == 888
// workaround for // [91, 32, -33686272] windows bug
println(nums.clone())
}
fn mod_struct(user mut User) {
user.age++
}
struct User {
mut:
age int
}
fn test_mut_struct() {
mut user := User{18}
mod_struct(mut user)
assert user.age == 19
}
fn mod_ptr(buf mut byteptr) {
buf[0] = 77
}
fn test_mut_ptr() {
buf := malloc(10)
mod_ptr(mut buf)
assert buf[0] == 77
}
fn high_fn(f fn(int) int) {
}
fn high_fn_array(f fn(a []int) []int) {
}
fn high_fn_multi_return(a int, b fn (c []int, d []string) ([]int, []string)) {
}
fn test_fns() {
// no asserts for now, just test function declarations above
}

View File

@ -0,0 +1,15 @@
struct VaTestGroup {
name string
}
fn variadic_test_a(name string, groups ...VaTestGroup) {
assert groups.len == 2
assert groups[0].name == 'users'
assert groups[1].name == 'admins'
}
fn test_fn_variadic() {
group1 := VaTestGroup{name: 'users'}
group2 := VaTestGroup{name: 'admins'}
variadic_test_a('joe', group1, group2)
}

View File

@ -0,0 +1,32 @@
struct Dog {
breed string
}
fn (d Dog) speak() {
println('dog.speak()')
}
fn (d Dog) name() string {
return 'old gray'
}
interface Speaker {
name() string
speak()
}
interface Speak2er {
speak()
name() string
}
fn perform_speak(s Speaker) bool {
s.speak()
return true
}
fn test_perform_speak() {
d := Dog{}
assert perform_speak(d)
}

View File

@ -0,0 +1,7 @@
module local
pub fn local_fn() bool {
return true
}

View File

@ -0,0 +1,7 @@
import compiler.tests.local
fn test_local_module_is_callable() {
assert local.local_fn()
}

View File

@ -0,0 +1,88 @@
enum Color{
red, green, blue
}
fn test_match_integers() {
// a := 3
// mut b := 0
// match a {
// 2 => println('two')
// 3 => println('three')
// b = 3
// 4 => println('four')
// else => println('???')
// }
// assert b == 3
assert match 2 {
1 => {2}
2 => {3}
else => {5}
} == 3
assert match 0 {
1 => {2}
2 => {3}
else => 5
} == 5
assert match 1 {
else => {5}
} == 5
mut a := 0
match 2 {
0 => {a = 1}
1 => {a = 2}
else => {
a = 3
println('a is $a')
}
}
assert a == 3
a = 0
match 1 {
0 => {a = 1}
1 => {
a = 2
a = a + 2
a = a + 2
}
}
assert a == 6
a = 0
match 1 {
else => {
a = -2
}
}
assert a == -2
}
fn test_match_enums(){
mut b := Color.red
match b{
.red => {
b = .green
}
.green => {b = .blue}
else => {
println('b is ${b.str()}')
b = .red
}
}
assert b == .green
match b{
.red => {
b = .green
}
else => {
println('b is ${b.str()}')
b = .blue
}
}
assert b == .blue
}

View File

@ -0,0 +1,18 @@
import os
import time as t
import crypto.sha256 as s2
import (
math
log as l
crypto.sha512 as s5
)
fn test_import() {
assert os.SUCCESS == os.SUCCESS &&
t.month_days[0] == t.month_days[0] &&
s2.Size == s2.Size &&
math.pi == math.pi &&
l.INFO == l.INFO &&
s5.Size == s5.Size
}

View File

@ -0,0 +1,49 @@
fn test_flag_parsing() {
mut rest := '-lGlfw -f gl2,-ltest_nice_meme,-l cc,-Ldl test.o a.o ' //, whatever.o'
result := ['-l', 'Glfw',
'-f', 'gl2',
'-l', 'test_nice_meme',
'-l', 'cc',
'-L', 'dl',
'', 'test.o',
'', 'a.o']
mut flags := []string
for {
mut base := rest
fl := if rest.starts_with('-') {
base = rest.right(2).trim_space()
rest.left(2)
} else {
''
}
// Which ever one of these is lowest we use
// TODO: we really shouldnt support all of these cmon
mut lowest := base.index('-')
for x in [base.index(' '), base.index(',')] {
if (x < lowest && x != -1) || lowest == -1 {
lowest = x
}
}
arg := if lowest != -1 {
rest = base.right(lowest).trim_space().trim(',')
base.left(lowest).trim_space().trim(',')
} else {
rest = ''
base.trim_space()
}
flags << fl
flags << arg
if rest.len == 0 {
break
}
}
for i, f in flags {
assert f == result[i]
}
}

View File

@ -0,0 +1,52 @@
struct A {
pub mut:
v []int
}
struct B {
pub mut:
a []A
}
fn foo(b int, a mut []int) {
a[0] = 7
//a << 4
}
fn test_mut() {
mut numbers := [1,2,3]
foo(7, mut numbers)
assert numbers.len == 3
// TODO bring back once << works with mutable args
//assert numbers.len == 4
//assert numbers[0] == 7
//assert numbers[3] == 4
println(numbers)
n := 1
mut b := &n
*b = 10
//mut b := mut a
//b = 10
}
fn test_mut_2() {
zero := 0
mut b := B{}
b.a << A{}
b.a[0].v = [9, 8, 7]
b.a[0].v << 6
b.a[zero].v << 5
b.a[0].v[zero] = 3
b.a[0].v[b.a[zero].v[zero]] += 1b.a[0].v[b.a[0].v[zero]] += 1
assert b.a[0].v.len == 5
assert b.a[0].v[0] == 3
assert b.a[0].v[1] == 8
assert b.a[0].v[2] == 7
assert b.a[0].v[3] == 8
assert b.a[0].v[4] == 5
}

View File

@ -0,0 +1,48 @@
fn opt_err() ?string {return error('hi')}
fn test_err(){
v := opt_err() or {
assert err == 'hi'
return
}
assert false
println(v) // suppress not used error
}
fn err_call(ok bool) ?int {
if !ok {
return error('Not ok!')
}
return 42
}
fn ret_none() ?int {
//return error('wtf') //none
return none
}
fn test_option_for_base_type_without_variable() {
val := err_call(true) or {
panic(err)
}
assert val == 42
println('hm')
val2 := ret_none() or {
println('yep')
return
}
println('$val2 should have been `none`')
assert false
// This is invalid:
// x := 5 or {
// return
// }
}
fn test_if_opt() {
if val := err_call(false) {
assert val == 42
}
assert 1 == 1
println('nice')
}

View File

@ -0,0 +1,2 @@
*.repl text=auto eol=lf

3
vlib/compiler/tests/repl/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
run
*.repl.result.txt
*.repl.expected.txt

View File

@ -0,0 +1,25 @@
# V REPL Tests Script
### How to write a new test
- Create a new file named `*.repl`
- Write the input to be given to REPL
- Add `===output===`
- Write the output expected
### Notes
Keep in mind, that the way V repl works for now, every non empty line
would cause a new recompilation of the entire repl content that was
collected so far.
*Longer REPL files would cause measurably*
*longer recompilation/testing times.*
Also, longer repl files would be slower to debug when they fail,
*It is better to have several smaller files vs one huge REPL file.*
### Example :
```
a := 1
println(a)
===output===
1

View File

@ -0,0 +1,4 @@
arr := ['1', '2', '3', '4']
println(arr)
===output===
["1", "2", "3", "4"]

View File

@ -0,0 +1,16 @@
struct A { mut: v int } struct B { a A } struct C { mut: b B } struct D { mut: c C } struct E { mut: v []int } struct F { e []E }
mut b := B{} b = B{A{2}}
println('b is: ' + b.a.v.str())
mut c := C{} c.b = B{}
mut d := D{} d.c.b = B{}
f := F{[E{[10,20,30]},E{[100,200,300,400]}]}
println('f.e[0].v.len: ${f.e[0].v.len}')
println('f.e[1].v.len: ${f.e[1].v.len}')
===output===
b is: 2
f.e[0].v.len: 3
f.e[1].v.len: 4

View File

@ -0,0 +1,78 @@
/* 1 */ struct A { mut: v int }
/* 2 */ struct B { a A }
/* 3 */ struct C { mut: b B }
/* 4 */ struct D { mut: c C }
/* 5 */ struct E { mut: v []int }
/* 6 */ struct F { e []E }
/* 7 */ mut s := 'hello world'
/*( 8)*/ s.len = 0 // Error (field len immutable)
/* 8 */ mut b := B{}
/*( 9)*/ b.a.v = 1 // Error (field a immutable)
/*( 9)*/ b.a = A{} // Error (field a immutable)
/* 9 */ b = B{A{2}} // Correct
/* 10 */ mut c := C{}
/* 11 */ c.b = B{} // Correct
/*(12)*/ c.b.a = A{} // Error (field a immutable)
/*(12)*/ c.b.a.v = 1 // Error (field a immutable)
/* 12 */ c2 := C{}
/*(13)*/ c2.b = B{} // Error (c2 immutable)
/* 13 */ mut d := D{}
/* 14 */ d.c.b = B{} // Correct
/* 15 */ mut f := F{}
/*(16)*/ f.e << E{} // Error (field e immutable)
/*(16)*/ f.e[0].v << 1 // Error (field e immutable)
/* 16 */ e := E{}
/*(17)*/ e.v << 1 // Error (e immutable)
===output===
.vrepl_temp.v:27:14: cannot modify immutable field `len` (type `string`)
declare the field with `mut:`
struct string {
mut:
len int
}
.vrepl_temp.v:30:14: cannot modify immutable field `a` (type `B`)
declare the field with `mut:`
struct B {
mut:
a A
}
.vrepl_temp.v:29:12: cannot modify immutable field `a` (type `B`)
declare the field with `mut:`
struct B {
mut:
a A
}
.vrepl_temp.v:41:14: cannot modify immutable field `a` (type `B`)
declare the field with `mut:`
struct B {
mut:
a A
}
.vrepl_temp.v:42:16: cannot modify immutable field `a` (type `B`)
declare the field with `mut:`
struct B {
mut:
a A
}
.vrepl_temp.v:44:15: `c2` is immutable
.vrepl_temp.v:53:12: cannot modify immutable field `e` (type `F`)
declare the field with `mut:`
struct F {
mut:
e []E
}
.vrepl_temp.v:54:17: cannot modify immutable field `e` (type `F`)
declare the field with `mut:`
struct F {
mut:
e []E
}
.vrepl_temp.v:57:17: `e` is immutable (can't <<)

View File

@ -0,0 +1,18 @@
if true {
println('foo')
}
for i := 0; i < 4; i++ {
println(i)
}
if false {
println('foo')
} else {
println('bar')
}
===output===
foo
0
1
2
3
bar

View File

@ -0,0 +1,7 @@
num := 1
string := 'Hello'
num
string
===output===
1
Hello

View File

@ -0,0 +1,4 @@
struct Empty{} ee := Empty{}
println('OK')
===output===
OK

View File

@ -0,0 +1,3 @@
println(a)
===output===
.vrepl.v:2:9: undefined: `a`

View File

@ -0,0 +1,5 @@
a
33
===output===
.vrepl_temp.v:3:9: undefined: `a`
33

View File

@ -0,0 +1,11 @@
fn test() {
println('foo')
}
test()
fn test2(a int) {
println(a)
}
test2(42)
===output===
foo
42

View File

@ -0,0 +1,30 @@
mut s := 'hello world'
s.len = 0 // Error (field len immutable)
mut a := []string
a.len = 0 // Error (field len immutable)
mut ints := []int
ints.len = 0 // Error (field len immutable)
println('BYE')
===output===
.vrepl_temp.v:3:5: cannot modify immutable field `len` (type `string`)
declare the field with `mut:`
struct string {
mut:
len int
}
.vrepl_temp.v:4:5: cannot modify immutable field `len` (type `array`)
declare the field with `mut:`
struct array {
mut:
len int
}
.vrepl_temp.v:5:8: cannot modify immutable field `len` (type `array`)
declare the field with `mut:`
struct array {
mut:
len int
}
BYE

View File

@ -0,0 +1,5 @@
a := 'Hello'
b := 'World'
println('$a $b')
===output===
Hello World

View File

@ -0,0 +1,10 @@
name := 'Bob'
age := 20
large_number := i64(9999999999)
println(name)
println(age)
println(large_number)
===output===
Bob
20
9999999999

View File

@ -0,0 +1,7 @@
println('Hello World')
println('Foo Bar')
println('dlroW olleH')
===output===
Hello World
Foo Bar
dlroW olleH

View File

@ -0,0 +1,9 @@
'abc'
'abc'+'xyz'
'{'
'}'
===output===
abc
abcxyz
{
}

View File

@ -0,0 +1,5 @@
===output===

View File

@ -0,0 +1 @@
===output===

View File

@ -0,0 +1,7 @@
println('Hello, world!')
println('Привет, мир!')
println('你好世界')
===output===
Hello, world!
Привет, мир!
你好世界

View File

@ -0,0 +1,40 @@
import os
import compiler.tests.repl.runner
import benchmark
fn test_the_v_compiler_can_be_invoked() {
vexec := runner.full_path_to_v()
println('vexecutable: $vexec')
assert vexec != ''
vcmd := '$vexec --version'
r := os.exec(vcmd) or { panic(err) }
//println('"$vcmd" exit_code: $r.exit_code | output: $r.output')
assert r.exit_code == 0
vcmd_error := '$vexec nonexisting.v'
r_error := os.exec(vcmd_error) or { panic(err) }
//println('"$vcmd_error" exit_code: $r_error.exit_code | output: $r_error.output')
assert r_error.exit_code == 1
assert r_error.output == '`nonexisting.v` does not exist'
}
fn test_all_v_repl_files() {
options := runner.new_options()
mut bmark := benchmark.new_benchmark()
for file in options.files {
bmark.step()
fres := runner.run_repl_file(options.wd, options.vexec, file) or {
bmark.fail()
eprintln( bmark.step_message(err) )
assert false
continue
}
bmark.ok()
println( bmark.step_message(fres) )
assert true
}
bmark.stop()
println( bmark.total_message('total time spent running REPL files') )
}

View File

@ -0,0 +1,24 @@
module main
import vcompiler.tests.repl.runner
import log
import benchmark
fn main(){
logger := &log.Log{log.DEBUG, 'terminal'}
options := runner.new_options()
mut bmark := benchmark.new_benchmark()
for file in options.files {
bmark.step()
fres := runner.run_repl_file(options.wd, options.vexec, file) or {
bmark.fail()
logger.error( bmark.step_message( err ) )
continue
}
bmark.ok()
logger.info( bmark.step_message( fres ) )
}
bmark.stop()
logger.info( bmark.total_message('total time spent running REPL files') )
}

View File

@ -0,0 +1,93 @@
module runner
import os
struct RunnerOptions {
pub:
wd string
vexec string
files []string
}
pub fn full_path_to_v() string {
vname := if os.user_os() == 'windows' { 'v.exe' } else { 'v' }
vexec := os.dir(os.dir(os.dir(os.dir(os.dir( os.executable() ))))) + os.path_separator + vname
/*
args := os.args
vreal := os.realpath('v')
myself := os.realpath( os.executable() )
wd := os.getwd() + os.path_separator
println('args are: $args')
println('vreal : $vreal')
println('myself : $myself')
println('wd : $wd')
*/
return vexec
}
fn find_working_diff_command() ?string {
for diffcmd in ['colordiff', 'diff', 'colordiff.exe', 'diff.exe'] {
p := os.exec('$diffcmd --version') or { continue }
if p.exit_code == 0 { return diffcmd }
}
return error('no working diff command found')
}
fn diff_files( file_result, file_expected string ) string {
diffcmd := find_working_diff_command() or { return err }
diff := os.exec('$diffcmd --minimal --text --unified=2 $file_result $file_expected') or { return 'found diff command "$diffcmd" does not work' }
return diff.output
}
pub fn run_repl_file(wd string, vexec string, file string) ?string {
fcontent := os.read_file(file) or { return error('Could not read file $file') }
content := fcontent.replace('\r', '')
input := content.all_before('===output===\n')
output := content.all_after('===output===\n')
input_temporary_filename := 'input_temporary_filename.txt'
os.write_file(input_temporary_filename, input)
r := os.exec('$vexec runrepl < $input_temporary_filename') or {
os.rm(input_temporary_filename)
return error('Could not execute "$vexec runrepl < $input_temporary_filename" ')
}
os.rm(input_temporary_filename)
result := r.output.replace('\r','').replace('>>> ', '').replace('>>>', '').replace('... ', '').all_after('Use Ctrl-C or `exit` to exit\n').replace(wd, '' )
if result != output {
file_result := '${file}.result.txt'
file_expected := '${file}.expected.txt'
os.write_file( file_result, result )
os.write_file( file_expected, output )
diff := diff_files( file_result, file_expected )
return error('Difference found in REPL file: $file
====> Got :
|$result|
====> Expected :
|$output|
====> Diff :
$diff
')
} else {
return 'Repl file $file is OK'
}
}
pub fn new_options() RunnerOptions {
wd := os.getwd() + os.path_separator
vexec := full_path_to_v()
mut files := []string
if os.args.len > 1 {
files = os.args.right(1)
} else {
files = os.walk_ext('.', '.repl')
}
return RunnerOptions {
wd: wd
vexec: vexec
files: files
}
}

View File

@ -0,0 +1,4 @@
a := 1
println(a)
===output===
1

View File

@ -0,0 +1,23 @@
struct Zest { val int }
fn (t Zest) get_a_finger_to_the_moon() voidptr {
return voidptr(0)
}
fn get_the_dao_way() voidptr {
return voidptr(0)
}
fn test_returning_a_void_pointer_from_a_method() {
t := &Zest{ val: 123 }
z := voidptr(0)
assert z == t.get_a_finger_to_the_moon()
assert t.get_a_finger_to_the_moon() == 0
}
fn test_returning_a_void_pointer_from_a_function() {
z := voidptr(0)
assert z == get_the_dao_way()
assert get_the_dao_way() == 0
}

View File

@ -0,0 +1,18 @@
struct Foo {
number int
str string
f f64
}
fn test_array_str() {
f := Foo{34, 'hello', 1.2}
println(f)
//s := f.str()
//println(s)
n := [1, 2, 3]
assert n.str() == '[1, 2, 3]'
println(n) // make sure the array is printable
n2 := [4,5,6]
//assert n2.str() == '[4, 5, 6]'
println(n2)
}

View File

@ -0,0 +1,29 @@
fn test_excape_dollar_in_string() {
i := 42
assert '($i)' == '(42)'
assert '(\$i)'.contains('i') && !'(\$i)'.contains('42')
assert !'(\\$i)'.contains('i') && '(\\$i)'.contains('42') && '(\\$i)'.contains('\\')
assert '(\\\$i)'.contains('i') && !'(\\\$i)'.contains('42') && '(\\$i)'.contains('\\')
assert !'(\\\\$i)'.contains('i') && '(\\\\$i)'.contains('42') && '(\\\\$i)'.contains('\\\\')
assert '(${i})' == '(42)'
assert '(\${i})'.contains('i') && !'(\${i})'.contains('42')
assert !'(\\${i})'.contains('i') && '(\\${i})'.contains('42') && '(\\${i})'.contains('\\')
assert '(\\\${i})'.contains('i') && !'(\\\${i})'.contains('42') && '(\\${i})'.contains('\\')
assert !'(\\\\${i})'.contains('i') && '(\\\\${i})'.contains('42') && '(\\\\${i})'.contains('\\\\')
assert i==42
}
fn test_implicit_str() {
i := 42
assert 'int $i' == 'int 42'
assert '$i' == '42'
check := '$i' == '42'
assert check
text := '$i' + '42'
assert text == '4242'
}

View File

@ -0,0 +1,120 @@
struct A{
mut:
val int
nums []int
}
struct B{
mut:
a A
}
struct C {
mut:
b B
nums []int
aarr []A
num int
}
struct User {
name string
age int
}
struct Foo {
@type string
}
//We need to make sure that this compiles with all the reserved names.
struct ReservedKeywords {
delete int
exit int
unix int
error int
malloc int
calloc int
free int
panic int
auto int
char int
do int
double int
extern int
float int
inline int
long int
register int
restrict int
short int
signed int
typedef int
unsigned int
void int
volatile int
while int
}
fn test_struct_levels() {
mut c := C{}
assert c.nums.len == 0
c.nums << 3
assert c.nums.len == 1
assert c.nums[0] == 3
c.nums[0] = 4
assert c.nums[0] == 4
c.b.a.val = 34
assert c.b.a.val == 34
c.b.a.nums = [0].repeat(0)
c.b.a.nums << 0
c.b.a.nums << 2
assert c.b.a.nums.len == 2
assert c.b.a.nums[0] == 0
assert c.b.a.nums[1] == 2
c.b.a.nums [0] = 7
assert c.b.a.nums[0] == 7
c.aarr << A{val:8}
assert c.aarr.len == 1
assert c.aarr[0].val == 8
c.num = 20
assert c.num == 20
c.aarr[0].val = 10
assert c.aarr[0].val == 10
}
fn test_struct_str() {
u := User{'Bob', 30}
println(u) // make sure the struct is printable
// assert u.str() == '{name:"Bob", age:30}' // TODO
}
fn test_at() {
foo := Foo{ @type: 'test' }
println(foo.@type)
}
fn test_reserved_keywords() {
//Make sure we can initialize them correctly using full syntax.
rk_holder := ReservedKeywords{0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}
//Test a few as it'll take too long to test all. If it's initialized
//correctly, other fields are also probably valid.
assert rk_holder.unix == 5
assert rk_holder.while == 3
rk_holder2 := ReservedKeywords{inline: 9, volatile: 11}
//Make sure partial initialization works too.
assert rk_holder2.inline == 9
assert rk_holder2.volatile == 11
assert rk_holder2.while == 0 //Zero value as not specified.
}
struct User2 {
mut:
name string
}
fn test_mutable_fields() {
mut u := User2{}
u.name = 'Peter'
assert u.name == 'Peter'
}

282
vlib/compiler/token.v Normal file
View File

@ -0,0 +1,282 @@
// 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 compiler
enum TokenKind {
eof
name // user
number // 123
str // 'foo'
str_inter // 'name=$user.name'
chartoken // `A`
plus
minus
mul
div
mod
xor // ^
pipe // |
inc // ++
dec // --
and // &&
logical_or
not
bit_not
question
comma
semicolon
colon
arrow // =>
amp
hash
dollar
left_shift
righ_shift
//at // @
assign // =
decl_assign // :=
plus_assign // +=
minus_assign // -=
div_assign
mult_assign
xor_assign
mod_assign
or_assign
and_assign
righ_shift_assign
left_shift_assign
// {} () []
lcbr
rcbr
lpar
rpar
lsbr
rsbr
// == != <= < >= >
eq
ne
gt
lt
ge
le
// comments
//line_com
//mline_com
nl
dot
dotdot
ellipsis
// keywords
keyword_beg
key_as
key_assert
key_atomic
key_break
key_case
key_const
key_continue
key_default
key_defer
key_else
key_embed
key_enum
key_false
key_for
func
key_global
key_go
key_goto
key_if
key_import
key_import_const
key_in
key_interface
key_match
key_module
key_mut
key_none
key_return
key_select
key_sizeof
key_struct
key_switch
key_true
key_type
//typeof
key_orelse
key_union
key_pub
key_static
keyword_end
}
// build_keys genereates a map with keywords' string values:
// Keywords['return'] == .key_return
fn build_keys() map[string]int {
mut res := map[string]int
for t := int(TokenKind.keyword_beg) + 1; t < int(TokenKind.keyword_end); t++ {
key := TokenStr[t]
res[key] = int(t)
}
return res
}
// TODO remove once we have `enum TokenKind { name('name') if('if') ... }`
fn build_token_str() []string {
mut s := [''].repeat(NrTokens)
s[TokenKind.keyword_beg] = ''
s[TokenKind.keyword_end] = ''
s[TokenKind.eof] = 'eof'
s[TokenKind.name] = 'name'
s[TokenKind.number] = 'number'
s[TokenKind.str] = 'STR'
s[TokenKind.chartoken] = 'char'
s[TokenKind.plus] = '+'
s[TokenKind.minus] = '-'
s[TokenKind.mul] = '*'
s[TokenKind.div] = '/'
s[TokenKind.mod] = '%'
s[TokenKind.xor] = '^'
s[TokenKind.bit_not] = '~'
s[TokenKind.pipe] = '|'
s[TokenKind.hash] = '#'
s[TokenKind.amp] = '&'
s[TokenKind.inc] = '++'
s[TokenKind.dec] = '--'
s[TokenKind.and] = '&&'
s[TokenKind.logical_or] = '||'
s[TokenKind.not] = '!'
s[TokenKind.dot] = '.'
s[TokenKind.dotdot] = '..'
s[TokenKind.ellipsis] = '...'
s[TokenKind.comma] = ','
//s[TokenKind.at] = '@'
s[TokenKind.semicolon] = ';'
s[TokenKind.colon] = ':'
s[TokenKind.arrow] = '=>'
s[TokenKind.assign] = '='
s[TokenKind.decl_assign] = ':='
s[TokenKind.plus_assign] = '+='
s[TokenKind.minus_assign] = '-='
s[TokenKind.mult_assign] = '*='
s[TokenKind.div_assign] = '/='
s[TokenKind.xor_assign] = '^='
s[TokenKind.mod_assign] = '%='
s[TokenKind.or_assign] = '|='
s[TokenKind.and_assign] = '&='
s[TokenKind.righ_shift_assign] = '>>='
s[TokenKind.left_shift_assign] = '<<='
s[TokenKind.lcbr] = '{'
s[TokenKind.rcbr] = '}'
s[TokenKind.lpar] = '('
s[TokenKind.rpar] = ')'
s[TokenKind.lsbr] = '['
s[TokenKind.rsbr] = ']'
s[TokenKind.eq] = '=='
s[TokenKind.ne] = '!='
s[TokenKind.gt] = '>'
s[TokenKind.lt] = '<'
s[TokenKind.ge] = '>='
s[TokenKind.le] = '<='
s[TokenKind.question] = '?'
s[TokenKind.left_shift] = '<<'
s[TokenKind.righ_shift] = '>>'
//s[TokenKind.line_com] = '//'
s[TokenKind.nl] = 'NLL'
s[TokenKind.dollar] = '$'
s[TokenKind.key_assert] = 'assert'
s[TokenKind.key_struct] = 'struct'
s[TokenKind.key_if] = 'if'
s[TokenKind.key_else] = 'else'
s[TokenKind.key_return] = 'return'
s[TokenKind.key_module] = 'module'
s[TokenKind.key_sizeof] = 'sizeof'
s[TokenKind.key_go] = 'go'
s[TokenKind.key_goto] = 'goto'
s[TokenKind.key_const] = 'const'
s[TokenKind.key_mut] = 'mut'
s[TokenKind.key_type] = 'type'
s[TokenKind.key_for] = 'for'
s[TokenKind.key_switch] = 'switch'
s[TokenKind.key_case] = 'case'
s[TokenKind.func] = 'fn'
s[TokenKind.key_true] = 'true'
s[TokenKind.key_false] = 'false'
s[TokenKind.key_continue] = 'continue'
s[TokenKind.key_break] = 'break'
s[TokenKind.key_import] = 'import'
s[TokenKind.key_embed] = 'embed'
//Tokens[key_typeof] = 'typeof'
s[TokenKind.key_default] = 'default'
s[TokenKind.key_enum] = 'enum'
s[TokenKind.key_interface] = 'interface'
s[TokenKind.key_pub] = 'pub'
s[TokenKind.key_import_const] = 'import_const'
s[TokenKind.key_in] = 'in'
s[TokenKind.key_atomic] = 'atomic'
s[TokenKind.key_orelse] = 'or'
s[TokenKind.key_global] = '__global'
s[TokenKind.key_union] = 'union'
s[TokenKind.key_static] = 'static'
s[TokenKind.key_as] = 'as'
s[TokenKind.key_defer] = 'defer'
s[TokenKind.key_match] = 'match'
s[TokenKind.key_select] = 'select'
s[TokenKind.key_none] = 'none'
return s
}
const (
NrTokens = 140
TokenStr = build_token_str()
KEYWORDS = build_keys()
)
fn key_to_token(key string) TokenKind {
a := TokenKind(KEYWORDS[key])
return a
}
fn is_key(key string) bool {
return int(key_to_token(key)) > 0
}
fn (t TokenKind) str() string {
return TokenStr[int(t)]
}
fn (t TokenKind) is_decl() bool {
// TODO i
//return t in [.key_enum, .key_interface, .func, .typ, .key_const,
//.key_import_const, .key_struct, .key_pub, .eof]
return t == .key_enum || t == .key_interface || t == .func ||
t == .key_struct || t == .key_type ||
t == .key_const || t == .key_import_const || t == .key_pub || t == .eof
}
const (
AssignTokens = [
TokenKind.assign, TokenKind.plus_assign, TokenKind.minus_assign,
TokenKind.mult_assign, TokenKind.div_assign, TokenKind.xor_assign,
TokenKind.mod_assign,
TokenKind.or_assign, TokenKind.and_assign, TokenKind.righ_shift_assign,
TokenKind.left_shift_assign
]
)
fn (t TokenKind) is_assign() bool {
return t in AssignTokens
}
fn (t []TokenKind) contains(val TokenKind) bool {
for tt in t {
if tt == val {
return true
}
}
return false
}

60
vlib/compiler/vfmt.v Normal file
View File

@ -0,0 +1,60 @@
// 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 compiler
import strings
// fmt helpers
fn (scanner mut Scanner) fgen(s_ string) {
mut s := s_
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) {
mut s := s_
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() TokenKind {
for {
p.cgen.line = p.scanner.line_nr + 1
tok := p.scanner.peek()
if tok != .nl {
return tok
}
}
return .eof // TODO can never get here - v doesn't know that
}
*/
fn (p mut Parser) fmt_inc() {
p.scanner.fmt_indent++
}
fn (p mut Parser) fmt_dec() {
p.scanner.fmt_indent--
}

82
vlib/compiler/vhelp.v Normal file
View File

@ -0,0 +1,82 @@
module compiler
const (
HelpText = 'Usage: v [options/commands] [file.v | directory]
When V is run without any arguments, it is run in REPL mode.
When given a .v file, it will be compiled. The executable will have the
same name as the input .v file: `v foo.v` produces `./foo` on *nix systems,
`foo.exe` on Windows.
You can use -o to specify a different output executable\'s name.
When given a directory, all .v files contained in it will be compiled as
part of a single main module.
By default the executable will have the same name as the directory.
To compile all V files in current directory, run `v .`
Any file ending in _test.v, will be treated as a test.
It will be compiled and run, evaluating the assert statements in every
function named test_xxx.
You can put common options inside an environment variable named VFLAGS, so that
you don\'t have to repeat them.
You can set it like this: `export VFLAGS="-cc clang -debug"` on *nix,
`set VFLAGS=-os msvc` on Windows.
Options/commands:
-h, help Display this information.
-o <file> Write output to <file>.
-o <file>.c Produce C source without compiling it.
-o <file>.js Produce JavaScript source.
-prod Build an optimized executable.
-v, version Display compiler version and git hash of the compiler source.
-live Enable hot code reloading (required by functions marked with [live]).
-os <OS> Produce an executable for the selected OS.
OS can be linux, mac, windows, msvc.
Use msvc if you want to use the MSVC compiler on Windows.
-shared Build a shared library.
-stats Show additional stats when compiling/running tests. Try `v -stats test .`
-cache Turn on usage of the precompiled module cache.
It very significantly speeds up secondary compilations.
-obf Obfuscate the resulting binary.
- Shorthand for `v runrepl`.
Options for debugging/troubleshooting v programs:
-g Generate debugging information in the backtraces. Add *V* line numbers to the generated executable.
-cg Same as -g, but add *C* line numbers to the generated executable instead of *V* line numbers.
-keep_c Do NOT remove the generated .tmp.c files after compilation.
It is useful when using debuggers like gdb/visual studio, when given after -g / -cg .
-show_c_cmd Print the full C compilation command and how much time it took.
-cc <ccompiler> Specify which C compiler you want to use as a C backend.
The C backend compiler should be able to handle C99 compatible C code.
Common C compilers are gcc, clang, tcc, icc, cl...
-cflags <flags> Pass additional C flags to the C backend compiler.
Example: -cflags `sdl2-config --cflags`
Commands:
up Update V. Run `v up` at least once per day, since V development is rapid and features/bugfixes are added constantly.
run <file.v> Build and execute the V program in file.v. You can add arguments for the V program *after* the file name.
build <module> Compile a module into an object file.
runrepl Run the V REPL. If V is running in a tty terminal, the REPL is interactive, otherwise it just reads from stdin.
symlink Useful on unix systems. Symlinks the current V executable to /usr/local/bin/v, so that V is globally available.
install <module> Install a user module from https://vpm.vlang.io/.
test v Run all V test files, and compile all V examples.
test folder/ Run all V test files located in the folder and its subfolders. You can also pass individual _test.v files too.
fmt Run vfmt to format the source code. [wip]
doc Run vdoc over the source code and produce documentation. [wip]
translate Translates C to V. [wip, will be available in V 0.3]
'
)
/*
- To disable automatic formatting:
v -nofmt file.v
*/

182
vlib/compiler/vtest.v Normal file
View File

@ -0,0 +1,182 @@
module compiler
import (
os
term
benchmark
)
struct TestSession {
mut:
files []string
vexe string
vargs string
failed bool
benchmark benchmark.Benchmark
}
pub fn new_test_sesion(vargs string) TestSession {
return TestSession{
vexe: os.executable()
vargs: vargs
}
}
pub fn test_v() {
args := os.args
if args.last() == 'test' {
println('Usage:')
println(' A)')
println(' v test v : run all v tests and build all the examples')
println(' B)')
println(' v test folder/ : run all v tests in the given folder.')
println(' v -stats test folder/ : the same, but print more stats.')
println(' C)')
println(' v test file_test.v : run test functions in a given test file.')
println(' v -stats test file_test.v : as above, but with more stats.')
println(' NB: you can also give many and mixed folder/ file_test.v arguments after test.')
println('')
return
}
args_string := args.right(1).join(' ')
args_before := args_string.all_before('test ')
args_after := args_string.all_after('test ')
if args_after == 'v' {
v_test_v(args_before)
return
}
mut ts := new_test_sesion(args_before)
for targ in args_after.split(' ') {
if os.file_exists(targ) && targ.ends_with('_test.v') {
ts.files << targ
continue
}
if os.dir_exists(targ) {
ts.files << os.walk_ext( targ.trim_right(os.path_separator), '_test.v')
continue
}
println('Unrecognized test file $targ .')
}
println('Testing...')
ts.test()
println('----------------------------------------------------------------------------')
println( ts.benchmark.total_message('running V _test.v files') )
if ts.failed {
exit(1)
}
}
pub fn (ts mut TestSession) test() {
ok := term.ok_message('OK')
fail := term.fail_message('FAIL')
cmd_needs_quoting := (os.user_os() == 'windows')
show_stats := '-stats' in ts.vargs.split(' ')
ts.benchmark = benchmark.new_benchmark()
for dot_relative_file in ts.files {
relative_file := dot_relative_file.replace('./', '')
file := os.realpath( relative_file )
tmpc_filepath := file.replace('.v', '.tmp.c')
mut cmd := '"$ts.vexe" $ts.vargs "$file"'
if cmd_needs_quoting { cmd = '"$cmd"' }
ts.benchmark.step()
if show_stats {
println('-------------------------------------------------')
status := os.system(cmd)
if status == 0 {
ts.benchmark.ok()
}else{
ts.benchmark.fail()
ts.failed = true
continue
}
}else{
r := os.exec(cmd) or {
ts.benchmark.fail()
ts.failed = true
println(ts.benchmark.step_message('$relative_file $fail'))
continue
}
if r.exit_code != 0 {
ts.benchmark.fail()
ts.failed = true
println(ts.benchmark.step_message('$relative_file $fail\n`$file`\n (\n$r.output\n)'))
} else {
ts.benchmark.ok()
println(ts.benchmark.step_message('$relative_file $ok'))
}
}
os.rm( tmpc_filepath )
}
ts.benchmark.stop()
}
fn stable_example(example string, index int, arr []string) bool {
return !example.contains('vweb')
}
pub fn v_test_v(args_before_test string){
vexe := os.executable()
parent_dir := os.dir(vexe)
// Changing the current directory is needed for some of the compiler tests,
// compiler/tests/local_test.v and compiler/tests/repl/repl_test.v
os.chdir( parent_dir )
if !os.dir_exists(parent_dir + '/vlib') {
println('vlib/ is missing, it must be next to the V executable')
exit(1)
}
if !os.dir_exists(parent_dir + '/compiler') {
println('compiler/ is missing, it must be next to the V executable')
exit(1)
}
// Make sure v.c can be compiled without warnings
$if mac {
os.system('$vexe -o v.c compiler')
if os.system('cc -Werror v.c') != 0 {
println('cc failed to build v.c without warnings')
exit(1)
}
println('v.c can be compiled without warnings. This is good :)')
}
//////////////////////////////////////////////////////////////
println('Testing...')
mut ts := new_test_sesion( args_before_test )
ts.files << os.walk_ext(parent_dir, '_test.v')
ts.test()
println( ts.benchmark.total_message('running V tests') )
//////////////////////////////////////////////////////////////
println('\nBuilding examples...')
mut es := new_test_sesion( args_before_test )
es.files << os.walk_ext(parent_dir+'/examples','.v').filter(stable_example)
es.test()
println( es.benchmark.total_message('building examples') )
//////////////////////////////////////////////////////////////
test_vget()
if ts.failed || es.failed {
exit(1)
}
}
pub fn test_vget() {
/*
vexe := os.executable()
ret := os.system('$vexe install nedpals.args')
if ret != 0 {
println('failed to run v install')
exit(1)
}
if !os.file_exists(v_modules_path + '/nedpals/args') {
println('v failed to install a test module')
exit(1)
}
println('vget is OK')
*/
}