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:

committed by
Alexander Medvednikov

parent
59d4535f84
commit
53c64abdeb
421
vlib/compiler/cc.v
Normal file
421
vlib/compiler/cc.v
Normal 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
190
vlib/compiler/cflags.v
Normal 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
369
vlib/compiler/cgen.v
Normal 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
172
vlib/compiler/cheaders.v
Normal 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() {}
|
||||
|
||||
'
|
||||
)
|
270
vlib/compiler/compile_errors.v
Normal file
270
vlib/compiler/compile_errors.v
Normal 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
319
vlib/compiler/comptime.v
Normal 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
130
vlib/compiler/depgraph.v
Normal 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
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
556
vlib/compiler/gen_c.v
Normal 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
249
vlib/compiler/gen_js.v
Normal 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
175
vlib/compiler/jsgen.v
Normal 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
220
vlib/compiler/live.v
Normal 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
1043
vlib/compiler/main.v
Normal file
File diff suppressed because it is too large
Load Diff
262
vlib/compiler/module_header.v
Normal file
262
vlib/compiler/module_header.v
Normal 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
70
vlib/compiler/modules.v
Normal 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
466
vlib/compiler/msvc.v
Normal 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 }
|
||||
}
|
40
vlib/compiler/optimization.v
Normal file
40
vlib/compiler/optimization.v
Normal 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
4160
vlib/compiler/parser.v
Normal file
File diff suppressed because it is too large
Load Diff
169
vlib/compiler/parser2.v
Normal file
169
vlib/compiler/parser2.v
Normal 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
242
vlib/compiler/query.v
Normal 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
180
vlib/compiler/repl.v
Normal 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
808
vlib/compiler/scanner.v
Normal 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
1035
vlib/compiler/table.v
Normal file
File diff suppressed because it is too large
Load Diff
21
vlib/compiler/tests/bench/val_vs_ptr.c
Normal file
21
vlib/compiler/tests/bench/val_vs_ptr.c
Normal 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;
|
||||
}
|
||||
|
51
vlib/compiler/tests/defer_test.v
Normal file
51
vlib/compiler/tests/defer_test.v
Normal 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
|
||||
}
|
24
vlib/compiler/tests/enum_test.v
Normal file
24
vlib/compiler/tests/enum_test.v
Normal 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
|
||||
}
|
32
vlib/compiler/tests/fixed_array_test.v
Normal file
32
vlib/compiler/tests/fixed_array_test.v
Normal 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] )
|
||||
*/
|
||||
}
|
19
vlib/compiler/tests/fn_multiple_returns_test.v
Normal file
19
vlib/compiler/tests/fn_multiple_returns_test.v
Normal 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
|
||||
}
|
121
vlib/compiler/tests/fn_test.v
Normal file
121
vlib/compiler/tests/fn_test.v
Normal 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
|
||||
}
|
||||
|
15
vlib/compiler/tests/fn_variadic_test.v
Normal file
15
vlib/compiler/tests/fn_variadic_test.v
Normal 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)
|
||||
}
|
32
vlib/compiler/tests/interface_test.v
Normal file
32
vlib/compiler/tests/interface_test.v
Normal 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)
|
||||
}
|
||||
|
7
vlib/compiler/tests/local/local.v
Normal file
7
vlib/compiler/tests/local/local.v
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
module local
|
||||
|
||||
pub fn local_fn() bool {
|
||||
return true
|
||||
}
|
||||
|
7
vlib/compiler/tests/local_test.v
Normal file
7
vlib/compiler/tests/local_test.v
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
import compiler.tests.local
|
||||
|
||||
fn test_local_module_is_callable() {
|
||||
assert local.local_fn()
|
||||
}
|
||||
|
88
vlib/compiler/tests/match_test.v
Normal file
88
vlib/compiler/tests/match_test.v
Normal 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
|
||||
}
|
18
vlib/compiler/tests/module_test.v
Normal file
18
vlib/compiler/tests/module_test.v
Normal 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
|
||||
}
|
49
vlib/compiler/tests/msvc_test.v
Normal file
49
vlib/compiler/tests/msvc_test.v
Normal 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]
|
||||
}
|
||||
}
|
52
vlib/compiler/tests/mut_test.v
Normal file
52
vlib/compiler/tests/mut_test.v
Normal 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
|
||||
}
|
48
vlib/compiler/tests/option_test.v
Normal file
48
vlib/compiler/tests/option_test.v
Normal 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')
|
||||
}
|
2
vlib/compiler/tests/repl/.gitattributes
vendored
Normal file
2
vlib/compiler/tests/repl/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
*.repl text=auto eol=lf
|
3
vlib/compiler/tests/repl/.gitignore
vendored
Normal file
3
vlib/compiler/tests/repl/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
run
|
||||
*.repl.result.txt
|
||||
*.repl.expected.txt
|
25
vlib/compiler/tests/repl/README.md
Normal file
25
vlib/compiler/tests/repl/README.md
Normal 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
|
4
vlib/compiler/tests/repl/arr_decl.repl
Normal file
4
vlib/compiler/tests/repl/arr_decl.repl
Normal file
@ -0,0 +1,4 @@
|
||||
arr := ['1', '2', '3', '4']
|
||||
println(arr)
|
||||
===output===
|
||||
["1", "2", "3", "4"]
|
16
vlib/compiler/tests/repl/chained_fields.correct.repl
Normal file
16
vlib/compiler/tests/repl/chained_fields.correct.repl
Normal 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
|
78
vlib/compiler/tests/repl/chained_fields.repl
Normal file
78
vlib/compiler/tests/repl/chained_fields.repl
Normal 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 <<)
|
18
vlib/compiler/tests/repl/conditional_blocks.repl
Normal file
18
vlib/compiler/tests/repl/conditional_blocks.repl
Normal 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
|
7
vlib/compiler/tests/repl/default_printing.repl
Normal file
7
vlib/compiler/tests/repl/default_printing.repl
Normal file
@ -0,0 +1,7 @@
|
||||
num := 1
|
||||
string := 'Hello'
|
||||
num
|
||||
string
|
||||
===output===
|
||||
1
|
||||
Hello
|
4
vlib/compiler/tests/repl/empty_struct.repl
Normal file
4
vlib/compiler/tests/repl/empty_struct.repl
Normal file
@ -0,0 +1,4 @@
|
||||
struct Empty{} ee := Empty{}
|
||||
println('OK')
|
||||
===output===
|
||||
OK
|
3
vlib/compiler/tests/repl/error.repl
Normal file
3
vlib/compiler/tests/repl/error.repl
Normal file
@ -0,0 +1,3 @@
|
||||
println(a)
|
||||
===output===
|
||||
.vrepl.v:2:9: undefined: `a`
|
5
vlib/compiler/tests/repl/error_nosave.repl
Normal file
5
vlib/compiler/tests/repl/error_nosave.repl
Normal file
@ -0,0 +1,5 @@
|
||||
a
|
||||
33
|
||||
===output===
|
||||
.vrepl_temp.v:3:9: undefined: `a`
|
||||
33
|
11
vlib/compiler/tests/repl/function.repl
Normal file
11
vlib/compiler/tests/repl/function.repl
Normal file
@ -0,0 +1,11 @@
|
||||
fn test() {
|
||||
println('foo')
|
||||
}
|
||||
test()
|
||||
fn test2(a int) {
|
||||
println(a)
|
||||
}
|
||||
test2(42)
|
||||
===output===
|
||||
foo
|
||||
42
|
30
vlib/compiler/tests/repl/immutable_len_fields.repl
Normal file
30
vlib/compiler/tests/repl/immutable_len_fields.repl
Normal 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
|
5
vlib/compiler/tests/repl/interpolation.repl
Normal file
5
vlib/compiler/tests/repl/interpolation.repl
Normal file
@ -0,0 +1,5 @@
|
||||
a := 'Hello'
|
||||
b := 'World'
|
||||
println('$a $b')
|
||||
===output===
|
||||
Hello World
|
10
vlib/compiler/tests/repl/multiple_decl.repl
Normal file
10
vlib/compiler/tests/repl/multiple_decl.repl
Normal file
@ -0,0 +1,10 @@
|
||||
name := 'Bob'
|
||||
age := 20
|
||||
large_number := i64(9999999999)
|
||||
println(name)
|
||||
println(age)
|
||||
println(large_number)
|
||||
===output===
|
||||
Bob
|
||||
20
|
||||
9999999999
|
7
vlib/compiler/tests/repl/multiple_println.repl
Normal file
7
vlib/compiler/tests/repl/multiple_println.repl
Normal file
@ -0,0 +1,7 @@
|
||||
println('Hello World')
|
||||
println('Foo Bar')
|
||||
println('dlroW olleH')
|
||||
===output===
|
||||
Hello World
|
||||
Foo Bar
|
||||
dlroW olleH
|
9
vlib/compiler/tests/repl/naked_strings.repl
Normal file
9
vlib/compiler/tests/repl/naked_strings.repl
Normal file
@ -0,0 +1,9 @@
|
||||
'abc'
|
||||
'abc'+'xyz'
|
||||
'{'
|
||||
'}'
|
||||
===output===
|
||||
abc
|
||||
abcxyz
|
||||
{
|
||||
}
|
5
vlib/compiler/tests/repl/newlines.repl
Normal file
5
vlib/compiler/tests/repl/newlines.repl
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
===output===
|
1
vlib/compiler/tests/repl/nothing.repl
Normal file
1
vlib/compiler/tests/repl/nothing.repl
Normal file
@ -0,0 +1 @@
|
||||
===output===
|
7
vlib/compiler/tests/repl/println.repl
Normal file
7
vlib/compiler/tests/repl/println.repl
Normal file
@ -0,0 +1,7 @@
|
||||
println('Hello, world!')
|
||||
println('Привет, мир!')
|
||||
println('你好世界')
|
||||
===output===
|
||||
Hello, world!
|
||||
Привет, мир!
|
||||
你好世界
|
40
vlib/compiler/tests/repl/repl_test.v
Normal file
40
vlib/compiler/tests/repl/repl_test.v
Normal 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') )
|
||||
}
|
||||
|
24
vlib/compiler/tests/repl/run.v
Normal file
24
vlib/compiler/tests/repl/run.v
Normal 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') )
|
||||
}
|
93
vlib/compiler/tests/repl/runner/runner.v
Normal file
93
vlib/compiler/tests/repl/runner/runner.v
Normal 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
|
||||
}
|
||||
}
|
||||
|
4
vlib/compiler/tests/repl/var_decl.repl
Normal file
4
vlib/compiler/tests/repl/var_decl.repl
Normal file
@ -0,0 +1,4 @@
|
||||
a := 1
|
||||
println(a)
|
||||
===output===
|
||||
1
|
23
vlib/compiler/tests/return_voidptr_test.v
Normal file
23
vlib/compiler/tests/return_voidptr_test.v
Normal 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
|
||||
}
|
18
vlib/compiler/tests/str_gen_test.v
Normal file
18
vlib/compiler/tests/str_gen_test.v
Normal 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)
|
||||
}
|
29
vlib/compiler/tests/string_interpolation_test.v
Normal file
29
vlib/compiler/tests/string_interpolation_test.v
Normal 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'
|
||||
}
|
120
vlib/compiler/tests/struct_test.v
Normal file
120
vlib/compiler/tests/struct_test.v
Normal 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
282
vlib/compiler/token.v
Normal 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
60
vlib/compiler/vfmt.v
Normal 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
82
vlib/compiler/vhelp.v
Normal 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
182
vlib/compiler/vtest.v
Normal 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')
|
||||
*/
|
||||
}
|
Reference in New Issue
Block a user