remove 15k lines of code of the old backend; make V2 the default backend
parent
30f306dc1a
commit
8dfb14b1c4
|
@ -74,7 +74,9 @@ pub fn (ts mut TestSession) test() {
|
||||||
}
|
}
|
||||||
ts.files = remaining_files
|
ts.files = remaining_files
|
||||||
ts.benchmark.set_total_expected_steps(remaining_files.len)
|
ts.benchmark.set_total_expected_steps(remaining_files.len)
|
||||||
mut pool_of_test_runners := sync.new_pool_processor({
|
// QTODO
|
||||||
|
//mut pool_of_test_runners := sync.new_pool_processor({
|
||||||
|
mut pool_of_test_runners := sync.new_pool_processor(sync.PoolProcessorConfig{
|
||||||
callback: worker_trunner
|
callback: worker_trunner
|
||||||
})
|
})
|
||||||
pool_of_test_runners.set_shared_context(ts)
|
pool_of_test_runners.set_shared_context(ts)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
module compiler
|
module compile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
os
|
os
|
||||||
|
@ -10,6 +10,15 @@ import (
|
||||||
term
|
term
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pub const (
|
||||||
|
v_version = '0.1.26'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const (
|
||||||
|
v_modules_path = pref.default_module_path
|
||||||
|
)
|
||||||
|
|
||||||
fn todo() {
|
fn todo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,6 +228,8 @@ fn (v mut V) cc() {
|
||||||
a << '-c'
|
a << '-c'
|
||||||
}
|
}
|
||||||
else if v.pref.is_cache {
|
else if v.pref.is_cache {
|
||||||
|
/*
|
||||||
|
QTODO
|
||||||
builtin_o_path := os.join_path(v_modules_path, 'cache', 'vlib', 'builtin.o')
|
builtin_o_path := os.join_path(v_modules_path, 'cache', 'vlib', 'builtin.o')
|
||||||
a << builtin_o_path.replace('builtin.o', 'strconv.o') // TODO hack no idea why this is needed
|
a << builtin_o_path.replace('builtin.o', 'strconv.o') // TODO hack no idea why this is needed
|
||||||
if os.exists(builtin_o_path) {
|
if os.exists(builtin_o_path) {
|
||||||
|
@ -260,7 +271,9 @@ fn (v mut V) cc() {
|
||||||
a << '-framework Cocoa -framework Carbon'
|
a << '-framework Cocoa -framework Carbon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.pref.sanitize {
|
if v.pref.sanitize {
|
||||||
a << '-fsanitize=leak'
|
a << '-fsanitize=leak'
|
||||||
}
|
}
|
||||||
|
@ -367,7 +380,7 @@ start:
|
||||||
==================
|
==================
|
||||||
C error. This should never happen.
|
C error. This should never happen.
|
||||||
|
|
||||||
V compiler version: V $Version $vhash()
|
V compiler version: V $v_version $vhash()
|
||||||
Host OS: ${pref.get_host_os().str()}
|
Host OS: ${pref.get_host_os().str()}
|
||||||
Target OS: $v.pref.os.str()
|
Target OS: $v.pref.os.str()
|
||||||
|
|
||||||
|
@ -463,6 +476,8 @@ If you're confident that all of the above is true, please try running V with the
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (c mut V) cc_windows_cross() {
|
fn (c mut V) cc_windows_cross() {
|
||||||
|
/*
|
||||||
|
QTODO
|
||||||
println('Cross compiling for Windows...')
|
println('Cross compiling for Windows...')
|
||||||
if !c.pref.out_name.ends_with('.exe') {
|
if !c.pref.out_name.ends_with('.exe') {
|
||||||
c.pref.out_name += '.exe'
|
c.pref.out_name += '.exe'
|
||||||
|
@ -533,6 +548,7 @@ fn (c mut V) cc_windows_cross() {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
println('Done!')
|
println('Done!')
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (c &V) build_thirdparty_obj_files() {
|
fn (c &V) build_thirdparty_obj_files() {
|
||||||
|
@ -549,6 +565,38 @@ fn (c &V) build_thirdparty_obj_files() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (v &V) build_thirdparty_obj_file(path string, moduleflags []CFlag) {
|
||||||
|
obj_path := os.real_path(path)
|
||||||
|
if os.exists(obj_path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println('$obj_path not found, building it...')
|
||||||
|
parent := os.dir(obj_path)
|
||||||
|
files := os.ls(parent)or{
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mut cfiles := ''
|
||||||
|
for file in files {
|
||||||
|
if file.ends_with('.c') {
|
||||||
|
cfiles += '"' + os.real_path(parent + os.path_separator + file) + '" '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btarget := moduleflags.c_options_before_target()
|
||||||
|
atarget := moduleflags.c_options_after_target()
|
||||||
|
cmd := '$v.pref.ccompiler $v.pref.third_party_option $btarget -c -o "$obj_path" $cfiles $atarget '
|
||||||
|
res := os.exec(cmd)or{
|
||||||
|
println('failed thirdparty object build cmd: $cmd')
|
||||||
|
verror(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.exit_code != 0 {
|
||||||
|
println('failed thirdparty object build cmd: $cmd')
|
||||||
|
verror(res.output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println(res.output)
|
||||||
|
}
|
||||||
|
|
||||||
fn missing_compiler_info() string {
|
fn missing_compiler_info() string {
|
||||||
$if windows {
|
$if windows {
|
||||||
return 'https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows'
|
return 'https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows'
|
||||||
|
@ -578,3 +626,11 @@ fn error_context_lines(text, keyword string, before, after int) []string {
|
||||||
idx_e := if idx_s + after < lines.len { idx_s + after } else { lines.len }
|
idx_e := if idx_s + after < lines.len { idx_s + after } else { lines.len }
|
||||||
return lines[idx_s..idx_e]
|
return lines[idx_s..idx_e]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vhash() string {
|
||||||
|
mut buf := [50]byte
|
||||||
|
buf[0] = 0
|
||||||
|
C.snprintf(charptr(buf), 50, '%s', C.V_COMMIT_HASH)
|
||||||
|
return tos_clone(buf)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
module compiler
|
module compile
|
||||||
|
|
||||||
import os
|
import os
|
||||||
// parsed cflag
|
// parsed cflag
|
||||||
|
@ -24,6 +24,8 @@ fn (v &V) get_os_cflags() []CFlag {
|
||||||
ctimedefines << v.pref.compile_defines
|
ctimedefines << v.pref.compile_defines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QTODO
|
||||||
|
/*
|
||||||
for flag in v.table.cflags {
|
for flag in v.table.cflags {
|
||||||
if flag.os == '' || (flag.os == 'linux' && v.pref.os == .linux) || (flag.os == 'darwin' && v.pref.os == .mac) || (flag.os == 'freebsd' && v.pref.os == .freebsd) || (flag.os == 'windows' && v.pref.os == .windows) || (flag.os == 'mingw' && v.pref.os == .windows && v.pref.ccompiler != 'msvc') || (flag.os == 'solaris' && v.pref.os == .solaris) {
|
if flag.os == '' || (flag.os == 'linux' && v.pref.os == .linux) || (flag.os == 'darwin' && v.pref.os == .mac) || (flag.os == 'freebsd' && v.pref.os == .freebsd) || (flag.os == 'windows' && v.pref.os == .windows) || (flag.os == 'mingw' && v.pref.os == .windows && v.pref.ccompiler != 'msvc') || (flag.os == 'solaris' && v.pref.os == .solaris) {
|
||||||
flags << flag
|
flags << flag
|
||||||
|
@ -32,6 +34,7 @@ fn (v &V) get_os_cflags() []CFlag {
|
||||||
flags << flag
|
flags << flag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +66,8 @@ fn (cf &CFlag) format() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if cflag is in table
|
// check if cflag is in table
|
||||||
|
/*
|
||||||
|
QTODO
|
||||||
fn (table &Table) has_cflag(cflag CFlag) bool {
|
fn (table &Table) has_cflag(cflag CFlag) bool {
|
||||||
for cf in table.cflags {
|
for cf in table.cflags {
|
||||||
if cf.os == cflag.os && cf.name == cflag.name && cf.value == cflag.value {
|
if cf.os == cflag.os && cf.name == cflag.name && cf.value == cflag.value {
|
||||||
|
@ -146,6 +151,7 @@ fn (table mut Table) parse_cflag(cflag string, mod string, ctimedefines []string
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// TODO: implement msvc specific c_options_before_target and c_options_after_target ...
|
// TODO: implement msvc specific c_options_before_target and c_options_after_target ...
|
||||||
fn (cflags []CFlag) c_options_before_target_msvc() string {
|
fn (cflags []CFlag) c_options_before_target_msvc() string {
|
|
@ -5,16 +5,127 @@ module compile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
benchmark
|
benchmark
|
||||||
compiler
|
|
||||||
os
|
os
|
||||||
|
v.builder
|
||||||
|
v.pref
|
||||||
|
strings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pub struct V {
|
||||||
|
pub mut:
|
||||||
|
mod_file_cacher &builder.ModFileCacher // used during lookup for v.mod to support @VROOT
|
||||||
|
out_name_c string // name of the temporary C file
|
||||||
|
files []string // all V files that need to be parsed and compiled
|
||||||
|
compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .`
|
||||||
|
pref &pref.Preferences // all the preferences and settings extracted to a struct for reusability
|
||||||
|
vgen_buf strings.Builder // temporary buffer for generated V code (.str() etc)
|
||||||
|
file_parser_idx map[string]int // map absolute file path to v.parsers index
|
||||||
|
gen_parser_idx map[string]int
|
||||||
|
cached_mods []string
|
||||||
|
module_lookup_paths []string
|
||||||
|
|
||||||
|
v_fmt_all bool // << input set by cmd/tools/vfmt.v
|
||||||
|
v_fmt_file string // << file given by the user from cmd/tools/vfmt.v
|
||||||
|
v_fmt_file_result string // >> file with formatted output generated by vlib/compiler/vfmt.v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_v(pref &pref.Preferences) &V {
|
||||||
|
rdir := os.real_path(pref.path)
|
||||||
|
|
||||||
|
mut out_name_c := get_vtmp_filename(pref.out_name, '.tmp.c')
|
||||||
|
if pref.is_so {
|
||||||
|
out_name_c = get_vtmp_filename(pref.out_name, '.tmp.so.c')
|
||||||
|
}
|
||||||
|
|
||||||
|
mut vgen_buf := strings.new_builder(1000)
|
||||||
|
vgen_buf.writeln('module vgen\nimport strings')
|
||||||
|
compiled_dir:=if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
|
||||||
|
|
||||||
|
return &V{
|
||||||
|
mod_file_cacher: builder.new_mod_file_cacher()
|
||||||
|
compiled_dir:compiled_dir// if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
|
||||||
|
out_name_c: out_name_c
|
||||||
|
pref: pref
|
||||||
|
vgen_buf: vgen_buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// make v2 from v1
|
||||||
|
fn (v &V) new_v2() builder.Builder {
|
||||||
|
mut b := builder.new_builder(v.pref)
|
||||||
|
b = { b|
|
||||||
|
os: v.pref.os,
|
||||||
|
module_path: pref.default_module_path,
|
||||||
|
compiled_dir: v.compiled_dir,
|
||||||
|
module_search_paths: v.module_lookup_paths
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_vtmp_folder() string {
|
||||||
|
vtmp := os.join_path(os.temp_dir(), 'v')
|
||||||
|
if !os.is_dir(vtmp) {
|
||||||
|
os.mkdir(vtmp) or {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vtmp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_vtmp_filename(base_file_name string, postfix string) string {
|
||||||
|
vtmp := get_vtmp_folder()
|
||||||
|
return os.real_path(os.join_path(vtmp, os.file_name(os.real_path(base_file_name)) + postfix))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn (v mut V) compile_x64() {
|
||||||
|
$if !linux {
|
||||||
|
println('v -x64 can only generate Linux binaries for now')
|
||||||
|
println('You are not on a Linux system, so you will not ' + 'be able to run the resulting executable')
|
||||||
|
}
|
||||||
|
//v.files << v.v_files_from_dir(os.join_path(v.pref.vlib_path,'builtin','bare'))
|
||||||
|
v.files << v.pref.path
|
||||||
|
v.set_module_lookup_paths()
|
||||||
|
mut b := v.new_v2()
|
||||||
|
// move all this logic to v2
|
||||||
|
b.build_x64(v.files, v.pref.out_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn (v mut V) compile2() {
|
||||||
|
if os.user_os() != 'windows' && v.pref.ccompiler == 'msvc' {
|
||||||
|
verror('Cannot build with msvc on ${os.user_os()}')
|
||||||
|
}
|
||||||
|
//cgen.genln('// Generated by V')
|
||||||
|
//println('compile2()')
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
||||||
|
println('all .v files before:')
|
||||||
|
println(v.files)
|
||||||
|
}
|
||||||
|
// v1 compiler files
|
||||||
|
//v.add_v_files_to_compile()
|
||||||
|
//v.files << v.dir
|
||||||
|
// v2 compiler
|
||||||
|
v.files << v.get_builtin_files()
|
||||||
|
v.files << v.get_user_files()
|
||||||
|
v.set_module_lookup_paths()
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
||||||
|
println('all .v files:')
|
||||||
|
println(v.files)
|
||||||
|
}
|
||||||
|
mut b := v.new_v2()
|
||||||
|
b.build_c(v.files, v.out_name_c)// v.pref.out_name + '.c')
|
||||||
|
v.cc()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn compile(command string, args []string) {
|
pub fn compile(command string, args []string) {
|
||||||
// Construct the V object from command line arguments
|
// Construct the V object from command line arguments
|
||||||
parse_and_output_new_format(args)
|
parse_and_output_new_format(args)
|
||||||
prefs, remaining := parse_arguments(args)
|
prefs, remaining := parse_arguments(args)
|
||||||
check_for_common_mistake(args, prefs)
|
check_for_common_mistake(args, prefs)
|
||||||
mut v := compiler.new_v(prefs)
|
mut v := new_v(prefs)
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
println(args)
|
println(args)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +137,8 @@ pub fn compile(command string, args []string) {
|
||||||
v.compile2()
|
v.compile2()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
v.compile()
|
//v.compile()
|
||||||
|
v.compile2()
|
||||||
}
|
}
|
||||||
if v.pref.is_stats {
|
if v.pref.is_stats {
|
||||||
tmark.stop()
|
tmark.stop()
|
||||||
|
@ -35,10 +147,10 @@ pub fn compile(command string, args []string) {
|
||||||
if v.pref.is_test || v.pref.is_run {
|
if v.pref.is_test || v.pref.is_run {
|
||||||
run_compiled_executable_and_exit(v, remaining)
|
run_compiled_executable_and_exit(v, remaining)
|
||||||
}
|
}
|
||||||
v.finalize_compilation()
|
//v.finalize_compilation()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_compiled_executable_and_exit(v &compiler.V, remaining_args []string) {
|
pub fn run_compiled_executable_and_exit(v &V, remaining_args []string) {
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
println('============ running $v.pref.out_name ============')
|
println('============ running $v.pref.out_name ============')
|
||||||
}
|
}
|
||||||
|
@ -70,3 +182,227 @@ pub fn run_compiled_executable_and_exit(v &compiler.V, remaining_args []string)
|
||||||
}
|
}
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 'strings' => 'VROOT/vlib/strings'
|
||||||
|
// 'installed_mod' => '~/.vmodules/installed_mod'
|
||||||
|
// 'local_mod' => '/path/to/current/dir/local_mod'
|
||||||
|
fn (v mut V) set_module_lookup_paths() {
|
||||||
|
// Module search order:
|
||||||
|
// 0) V test files are very commonly located right inside the folder of the
|
||||||
|
// module, which they test. Adding the parent folder of the module folder
|
||||||
|
// with the _test.v files, *guarantees* that the tested module can be found
|
||||||
|
// without needing to set custom options/flags.
|
||||||
|
// 1) search in the *same* directory, as the compiled final v program source
|
||||||
|
// (i.e. the . in `v .` or file.v in `v file.v`)
|
||||||
|
// 2) search in the modules/ in the same directory.
|
||||||
|
// 3) search in the provided paths
|
||||||
|
// By default, these are what (3) contains:
|
||||||
|
// 3.1) search in vlib/
|
||||||
|
// 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm)
|
||||||
|
v.module_lookup_paths = []
|
||||||
|
if v.pref.is_test {
|
||||||
|
v.module_lookup_paths << os.base_dir(v.compiled_dir) // pdir of _test.v
|
||||||
|
}
|
||||||
|
v.module_lookup_paths << v.compiled_dir
|
||||||
|
x := os.join_path(v.compiled_dir, 'modules')
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
println('x: "$x"')
|
||||||
|
}
|
||||||
|
v.module_lookup_paths << os.join_path(v.compiled_dir, 'modules')
|
||||||
|
v.module_lookup_paths << v.pref.lookup_path
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
v.log('v.module_lookup_paths') //: $v.module_lookup_paths')
|
||||||
|
println(v.module_lookup_paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn verror(s string) {
|
||||||
|
println('V error: $s')
|
||||||
|
os.flush()
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (v &V) get_builtin_files() []string {
|
||||||
|
// Lookup for built-in folder in lookup path.
|
||||||
|
// Assumption: `builtin/` folder implies usable implementation of builtin
|
||||||
|
for location in v.pref.lookup_path {
|
||||||
|
if !os.exists(os.join_path(location, 'builtin')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v.pref.is_bare {
|
||||||
|
return v.v_files_from_dir(os.join_path(location, 'builtin', 'bare'))
|
||||||
|
}
|
||||||
|
$if js {
|
||||||
|
return v.v_files_from_dir(os.join_path(location, 'builtin', 'js'))
|
||||||
|
}
|
||||||
|
return v.v_files_from_dir(os.join_path(location, 'builtin'))
|
||||||
|
}
|
||||||
|
// Panic. We couldn't find the folder.
|
||||||
|
verror('`builtin/` not included on module lookup path.
|
||||||
|
Did you forget to add vlib to the path? (Use @vlib for default vlib)')
|
||||||
|
panic('Unreachable code reached.')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn (v &V) get_user_files() []string {
|
||||||
|
mut dir := v.pref.path
|
||||||
|
v.log('get_v_files($dir)')
|
||||||
|
// Need to store user files separately, because they have to be added after
|
||||||
|
// libs, but we dont know which libs need to be added yet
|
||||||
|
mut user_files := []string
|
||||||
|
|
||||||
|
// See cmd/tools/preludes/README.md for more info about what preludes are
|
||||||
|
vroot := os.dir(pref.vexe_path())
|
||||||
|
preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes')
|
||||||
|
if v.pref.is_live {
|
||||||
|
user_files << os.join_path(preludes_path, 'live_main.v')
|
||||||
|
}
|
||||||
|
if v.pref.is_solive {
|
||||||
|
user_files << os.join_path(preludes_path, 'live_shared.v')
|
||||||
|
}
|
||||||
|
if v.pref.is_test {
|
||||||
|
user_files << os.join_path(preludes_path, 'tests_assertions.v')
|
||||||
|
}
|
||||||
|
if v.pref.is_test && v.pref.is_stats {
|
||||||
|
user_files << os.join_path(preludes_path, 'tests_with_stats.v')
|
||||||
|
}
|
||||||
|
|
||||||
|
is_test := dir.ends_with('_test.v')
|
||||||
|
mut is_internal_module_test := false
|
||||||
|
if is_test {
|
||||||
|
tcontent := os.read_file(dir)or{
|
||||||
|
panic('$dir does not exist')
|
||||||
|
}
|
||||||
|
slines := tcontent.trim_space().split_into_lines()
|
||||||
|
for sline in slines {
|
||||||
|
line := sline.trim_space()
|
||||||
|
if line.len > 2 {
|
||||||
|
if line[0] == `/` && line[1] == `/` {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if line.starts_with('module ') && !line.starts_with('module main') {
|
||||||
|
is_internal_module_test = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_internal_module_test {
|
||||||
|
// v volt/slack_test.v: compile all .v files to get the environment
|
||||||
|
single_test_v_file := os.real_path(dir)
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
v.log('> Compiling an internal module _test.v file $single_test_v_file .')
|
||||||
|
v.log('> That brings in all other ordinary .v files in the same module too .')
|
||||||
|
}
|
||||||
|
user_files << single_test_v_file
|
||||||
|
dir = os.base_dir(single_test_v_file)
|
||||||
|
}
|
||||||
|
is_real_file := os.exists(dir) && !os.is_dir(dir)
|
||||||
|
if is_real_file && ( dir.ends_with('.v') || dir.ends_with('.vsh') ) {
|
||||||
|
single_v_file := dir
|
||||||
|
// Just compile one file and get parent dir
|
||||||
|
user_files << single_v_file
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
v.log('> just compile one file: "${single_v_file}"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
v.log('> add all .v files from directory "${dir}" ...')
|
||||||
|
}
|
||||||
|
// Add .v files from the directory being compiled
|
||||||
|
files := v.v_files_from_dir(dir)
|
||||||
|
for file in files {
|
||||||
|
user_files << file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user_files.len == 0 {
|
||||||
|
println('No input .v files')
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
v.log('user_files: $user_files')
|
||||||
|
}
|
||||||
|
return user_files
|
||||||
|
}
|
||||||
|
pub fn (v &V) log(s string) {
|
||||||
|
if !v.pref.verbosity.is_higher_or_equal(.level_two) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn (v &V) v_files_from_dir(dir string) []string {
|
||||||
|
mut res := []string
|
||||||
|
if !os.exists(dir) {
|
||||||
|
if dir == 'compiler' && os.is_dir('vlib') {
|
||||||
|
println('looks like you are trying to build V with an old command')
|
||||||
|
println('use `v -o v cmd/v` instead of `v -o v compiler`')
|
||||||
|
}
|
||||||
|
verror("$dir doesn't exist")
|
||||||
|
}
|
||||||
|
else if !os.is_dir(dir) {
|
||||||
|
verror("$dir isn't a directory!")
|
||||||
|
}
|
||||||
|
mut files := os.ls(dir)or{
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
||||||
|
println('v_files_from_dir ("$dir")')
|
||||||
|
}
|
||||||
|
files.sort()
|
||||||
|
for file in files {
|
||||||
|
if !file.ends_with('.v') && !file.ends_with('.vh') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_test.v') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (file.ends_with('_win.v') || file.ends_with('_windows.v')) && v.pref.os != .windows {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (file.ends_with('_lin.v') || file.ends_with('_linux.v')) && v.pref.os != .linux {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (file.ends_with('_mac.v') || file.ends_with('_darwin.v')) && v.pref.os != .mac {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_nix.v') && v.pref.os == .windows {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_android.v') && v.pref.os != .android {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_freebsd.v') && v.pref.os != .freebsd {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_solaris.v') && v.pref.os != .solaris {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_js.v') && v.pref.os != .js {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.ends_with('_c.v') && v.pref.os == .js {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v.pref.compile_defines_all.len > 0 && file.contains('_d_') {
|
||||||
|
mut allowed := false
|
||||||
|
for cdefine in v.pref.compile_defines {
|
||||||
|
file_postfix := '_d_${cdefine}.v'
|
||||||
|
if file.ends_with(file_postfix) {
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res << os.join_path(dir, file)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module compiler
|
module compile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
os
|
os
|
||||||
|
@ -20,6 +20,8 @@ fn (v &V) generate_hotcode_reloading_compiler_flags() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (v &V) generate_hotcode_reloading_declarations() {
|
fn (v &V) generate_hotcode_reloading_declarations() {
|
||||||
|
/*
|
||||||
|
QTODO
|
||||||
mut cgen := v.cgen
|
mut cgen := v.cgen
|
||||||
if v.pref.os != .windows {
|
if v.pref.os != .windows {
|
||||||
if v.pref.is_so {
|
if v.pref.is_so {
|
||||||
|
@ -45,9 +47,12 @@ void pthread_mutex_unlock(HANDLE *m) {
|
||||||
cgen.genln('HANDLE live_fn_mutex = 0;')
|
cgen.genln('HANDLE live_fn_mutex = 0;')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (v &V) generate_hotcode_reloading_main_caller() {
|
fn (v &V) generate_hotcode_reloading_main_caller() {
|
||||||
|
// QTODO
|
||||||
|
/*
|
||||||
if !v.pref.is_live {
|
if !v.pref.is_live {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -72,9 +77,12 @@ fn (v &V) generate_hotcode_reloading_main_caller() {
|
||||||
cgen.genln(' unsigned long _thread_so;')
|
cgen.genln(' unsigned long _thread_so;')
|
||||||
cgen.genln(' _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);')
|
cgen.genln(' _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);')
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (v &V) generate_hot_reload_code() {
|
fn (v &V) generate_hot_reload_code() {
|
||||||
|
/*
|
||||||
|
QTODO
|
||||||
mut cgen := v.cgen
|
mut cgen := v.cgen
|
||||||
// Hot code reloading
|
// Hot code reloading
|
||||||
if v.pref.is_live {
|
if v.pref.is_live {
|
||||||
|
@ -246,4 +254,5 @@ void reload_so() {
|
||||||
if v.pref.is_so {
|
if v.pref.is_so {
|
||||||
cgen.genln(' int load_so(byteptr path) { return 0; }')
|
cgen.genln(' int load_so(byteptr path) { return 0; }')
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
module compiler
|
module compile
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
|
@ -4,15 +4,23 @@
|
||||||
module main
|
module main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
compiler
|
|
||||||
os
|
os
|
||||||
v.pref
|
v.pref
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fn set_vroot_folder(vroot_path string) {
|
||||||
|
// Preparation for the compiler module:
|
||||||
|
// VEXE env variable is needed so that compiler.vexe_path()
|
||||||
|
// can return it later to whoever needs it:
|
||||||
|
vname := if os.user_os() == 'windows' { 'v.exe' } else { 'v' }
|
||||||
|
os.setenv('VEXE', os.real_path([vroot_path, vname].join(os.path_separator)), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn launch_tool(verbosity pref.VerboseLevel, tool_name string) {
|
fn launch_tool(verbosity pref.VerboseLevel, tool_name string) {
|
||||||
vexe := pref.vexe_path()
|
vexe := pref.vexe_path()
|
||||||
vroot := os.dir(vexe)
|
vroot := os.dir(vexe)
|
||||||
compiler.set_vroot_folder(vroot)
|
set_vroot_folder(vroot)
|
||||||
|
|
||||||
tool_args := os.args[1..].join(' ')
|
tool_args := os.args[1..].join(' ')
|
||||||
tool_exe := path_of_executable(os.real_path('$vroot/cmd/tools/$tool_name'))
|
tool_exe := path_of_executable(os.real_path('$vroot/cmd/tools/$tool_name'))
|
||||||
|
|
19
cmd/v/v.v
19
cmd/v/v.v
|
@ -4,7 +4,6 @@
|
||||||
module main
|
module main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
compiler
|
|
||||||
internal.compile
|
internal.compile
|
||||||
internal.flag
|
internal.flag
|
||||||
internal.help
|
internal.help
|
||||||
|
@ -24,6 +23,11 @@ const (
|
||||||
'setup-freetype']
|
'setup-freetype']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pub const (
|
||||||
|
v_version = '0.1.26'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
prefs := flag.MainCmdPreferences{}
|
prefs := flag.MainCmdPreferences{}
|
||||||
values := flag.parse_main_cmd(os.args, parse_flags, prefs) or {
|
values := flag.parse_main_cmd(os.args, parse_flags, prefs) or {
|
||||||
|
@ -32,7 +36,7 @@ fn main() {
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
if prefs.verbosity.is_higher_or_equal(.level_two) {
|
if prefs.verbosity.is_higher_or_equal(.level_two) {
|
||||||
println('V $compiler.Version $compiler.vhash()')
|
println('V $v_version $vhash()')
|
||||||
}
|
}
|
||||||
if prefs.verbosity.is_higher_or_equal(.level_three) {
|
if prefs.verbosity.is_higher_or_equal(.level_three) {
|
||||||
println('Parsed preferences: ')
|
println('Parsed preferences: ')
|
||||||
|
@ -122,11 +126,18 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_version_and_exit() {
|
fn print_version_and_exit() {
|
||||||
version_hash := compiler.vhash()
|
version_hash := vhash()
|
||||||
println('V $compiler.Version $version_hash')
|
println('V $v_version $version_hash')
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vhash() string {
|
||||||
|
mut buf := [50]byte
|
||||||
|
buf[0] = 0
|
||||||
|
C.snprintf(charptr(buf), 50, '%s', C.V_COMMIT_HASH)
|
||||||
|
return tos_clone(buf)
|
||||||
|
}
|
||||||
|
|
||||||
fn invoke_help_and_exit(remaining []string) {
|
fn invoke_help_and_exit(remaining []string) {
|
||||||
match remaining.len {
|
match remaining.len {
|
||||||
0, 1 {
|
0, 1 {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
fn (p mut Parser) inline_asm() {
|
|
||||||
if !p.inside_unsafe {
|
|
||||||
p.error('asm() needs to be run inside `unsafe {}`')
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.check(.lcbr)
|
|
||||||
s := p.check_string()
|
|
||||||
p.genln('asm("$s"')
|
|
||||||
for p.tok == .string{
|
|
||||||
p.genln('"$p.lit"')
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
for p.tok == .colon {
|
|
||||||
p.next()
|
|
||||||
arg := p.check_string()
|
|
||||||
p.gen(': "$arg"')
|
|
||||||
if p.tok == .lpar {
|
|
||||||
p.next()
|
|
||||||
var_name := p.check_name()
|
|
||||||
if !p.known_var(var_name) {
|
|
||||||
p.error('unknown variable `$var_name`')
|
|
||||||
}
|
|
||||||
p.check(.rpar)
|
|
||||||
p.genln('($var_name)')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.genln(');')
|
|
||||||
p.check(.rcbr)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,534 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
)
|
|
||||||
|
|
||||||
struct CGen {
|
|
||||||
out os.File
|
|
||||||
out_path string
|
|
||||||
// types []string
|
|
||||||
thread_fns []string
|
|
||||||
// buf strings.Builder
|
|
||||||
is_user bool
|
|
||||||
mut:
|
|
||||||
lines []string
|
|
||||||
lines_extra []string
|
|
||||||
typedefs []string
|
|
||||||
type_aliases []string
|
|
||||||
includes []string
|
|
||||||
thread_args []string
|
|
||||||
consts []string
|
|
||||||
const_defines []string
|
|
||||||
fns []string
|
|
||||||
so_fns []string
|
|
||||||
consts_init []string
|
|
||||||
pass Pass
|
|
||||||
nogen bool
|
|
||||||
prev_tmps []string
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_cgen(out_name_c string) &CGen {
|
|
||||||
path := out_name_c
|
|
||||||
out := os.create(path)or{
|
|
||||||
println('failed to create $path')
|
|
||||||
return &CGen{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &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 = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// same as `set_placeholder(0, s)`, but faster
|
|
||||||
fn (g mut CGen) prepend_to_statement(s string) {
|
|
||||||
if g.is_tmp {
|
|
||||||
g.tmp_line = s + g.tmp_line
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.lines << s
|
|
||||||
g.prev_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.writeln(g.lines_extra.join('\n'))
|
|
||||||
g.out.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns expression's type, and entire expression's string representation)
|
|
||||||
fn (p mut Parser) tmp_expr() (string,string) {
|
|
||||||
// former start_tmp()
|
|
||||||
if p.cgen.is_tmp {
|
|
||||||
p.cgen.prev_tmps << p.cgen.tmp_line
|
|
||||||
}
|
|
||||||
// kg.tmp_lines_pos++
|
|
||||||
p.cgen.tmp_line = ''
|
|
||||||
p.cgen.is_tmp = true
|
|
||||||
//
|
|
||||||
typ := p.bool_expression()
|
|
||||||
res := p.cgen.tmp_line
|
|
||||||
if p.cgen.prev_tmps.len > 0 {
|
|
||||||
p.cgen.tmp_line = p.cgen.prev_tmps.last()
|
|
||||||
p.cgen.prev_tmps = p.cgen.prev_tmps[0..p.cgen.prev_tmps.len - 1]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.cgen.tmp_line = ''
|
|
||||||
p.cgen.is_tmp = false
|
|
||||||
}
|
|
||||||
return typ,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[pos..]
|
|
||||||
g.tmp_line = g.tmp_line[..pos]
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
res := g.cur_line[pos..]
|
|
||||||
g.cur_line = g.cur_line[..pos]
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (g mut CGen) set_placeholder(pos int, val string) {
|
|
||||||
if g.nogen || g.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// if pos == 0 {
|
|
||||||
// g.prepend_to_statement(val)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// g.lines.set(pos, val)
|
|
||||||
if g.is_tmp {
|
|
||||||
left := g.tmp_line[..pos]
|
|
||||||
right := g.tmp_line[pos..]
|
|
||||||
g.tmp_line = '${left}${val}${right}'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
left := g.cur_line[..pos]
|
|
||||||
right := g.cur_line[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 {
|
|
||||||
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 {
|
|
||||||
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 (v &V) build_thirdparty_obj_file(path string, moduleflags []CFlag) {
|
|
||||||
obj_path := os.real_path(path)
|
|
||||||
if os.exists(obj_path) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
println('$obj_path not found, building it...')
|
|
||||||
parent := os.dir(obj_path)
|
|
||||||
files := os.ls(parent)or{
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
mut cfiles := ''
|
|
||||||
for file in files {
|
|
||||||
if file.ends_with('.c') {
|
|
||||||
cfiles += '"' + os.real_path(parent + os.path_separator + file) + '" '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
btarget := moduleflags.c_options_before_target()
|
|
||||||
atarget := moduleflags.c_options_after_target()
|
|
||||||
cmd := '$v.pref.ccompiler $v.pref.third_party_option $btarget -c -o "$obj_path" $cfiles $atarget '
|
|
||||||
res := os.exec(cmd)or{
|
|
||||||
println('failed thirdparty object build cmd: $cmd')
|
|
||||||
verror(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if res.exit_code != 0 {
|
|
||||||
println('failed thirdparty object build cmd: $cmd')
|
|
||||||
verror(res.output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
println(res.output)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn os_name_to_ifdef(name string) string {
|
|
||||||
match name {
|
|
||||||
'windows' {
|
|
||||||
return '_WIN32'
|
|
||||||
}
|
|
||||||
'mac' {
|
|
||||||
return '__APPLE__'
|
|
||||||
}
|
|
||||||
'macos' {
|
|
||||||
return '__APPLE__'
|
|
||||||
}
|
|
||||||
'linux' {
|
|
||||||
return '__linux__'
|
|
||||||
}
|
|
||||||
'freebsd' {
|
|
||||||
return '__FreeBSD__'
|
|
||||||
}
|
|
||||||
'openbsd' {
|
|
||||||
return '__OpenBSD__'
|
|
||||||
}
|
|
||||||
'netbsd' {
|
|
||||||
return '__NetBSD__'
|
|
||||||
}
|
|
||||||
'dragonfly' {
|
|
||||||
return '__DragonFly__'
|
|
||||||
}
|
|
||||||
'msvc' {
|
|
||||||
return '_MSC_VER'
|
|
||||||
}
|
|
||||||
'android' {
|
|
||||||
return '__ANDROID__'
|
|
||||||
}
|
|
||||||
'js' {
|
|
||||||
return '_VJS'
|
|
||||||
}
|
|
||||||
'solaris' {
|
|
||||||
return '__sun'
|
|
||||||
}
|
|
||||||
'haiku' {
|
|
||||||
return '__haiku__'
|
|
||||||
}
|
|
||||||
'linux_or_macos' {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
verror('bad os ifdef name "$name"')
|
|
||||||
}}
|
|
||||||
// verror('bad os ifdef name "$name"')
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (v &V) platform_postfix_to_ifdefguard(name string) string {
|
|
||||||
if name.starts_with('custom '){
|
|
||||||
cdefine := name.replace('custom ','')
|
|
||||||
return '#ifdef CUSTOM_DEFINE_${cdefine}'
|
|
||||||
}
|
|
||||||
s := match name {
|
|
||||||
'.v' {
|
|
||||||
''
|
|
||||||
} // no guard needed
|
|
||||||
'_win.v', '_windows.v' {
|
|
||||||
'#ifdef _WIN32'
|
|
||||||
}
|
|
||||||
'_nix.v' {
|
|
||||||
'#ifndef _WIN32'
|
|
||||||
}
|
|
||||||
'_qnx.v' {
|
|
||||||
'#ifndef __QNX__'
|
|
||||||
}
|
|
||||||
'_lin.v', '_linux.v' {
|
|
||||||
'#ifdef __linux__'
|
|
||||||
}
|
|
||||||
'_mac.v', '_darwin.v' {
|
|
||||||
'#ifdef __APPLE__'
|
|
||||||
}
|
|
||||||
'_freebsd.v' {
|
|
||||||
'#ifdef __FreeBSD__'
|
|
||||||
}
|
|
||||||
'_openbsd.v' {
|
|
||||||
'#ifdef __OpenBSD__'
|
|
||||||
}
|
|
||||||
'_netbsd.v' {
|
|
||||||
'#ifdef __NetBSD__'
|
|
||||||
}
|
|
||||||
'_bsd.v' {
|
|
||||||
'#ifdef __FreeBSD__ || __NetBSD__ || __OpenBSD__'
|
|
||||||
}
|
|
||||||
'_solaris.v' {
|
|
||||||
'#ifdef __sun'
|
|
||||||
}
|
|
||||||
'_haiku.v' {
|
|
||||||
'#ifdef __haiku__'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// verror('bad platform_postfix "$name"')
|
|
||||||
// TODO
|
|
||||||
''}}
|
|
||||||
if s == '' {
|
|
||||||
verror('bad platform_postfix "$name"')
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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', 'KeyValue', 'DenseArray', '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 || t.is_generic {
|
|
||||||
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 typ in types {
|
|
||||||
type_names << typ.name
|
|
||||||
}
|
|
||||||
// loop over types
|
|
||||||
for t in types {
|
|
||||||
// create list of deps
|
|
||||||
mut field_deps := []string
|
|
||||||
for field in t.fields {
|
|
||||||
// Need to handle fixed size arrays as well (`[10]Point`)
|
|
||||||
ft := if field.typ.starts_with('[') { field.typ.all_after(']') } else { field.typ }
|
|
||||||
// skip if not in types list or already in deps
|
|
||||||
if !(ft in type_names) || ft in field_deps {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
field_deps << ft // 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('cgen.sort_structs(): the following structs form a dependency cycle:\n' + dep_graph_sorted.display_cycles() + '\nyou can solve this by making one or both of the dependant struct fields references, eg: field &MyStruct' + '\nif you feel this is an error, please 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates interface table and interface indexes
|
|
||||||
fn (v &V) interface_table() string {
|
|
||||||
mut sb := strings.new_builder(100)
|
|
||||||
for _, t in v.table.typesmap {
|
|
||||||
if t.cat != .interface_ {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// interface_name is for example Speaker
|
|
||||||
interface_name := t.name
|
|
||||||
mut methods := ''
|
|
||||||
mut generated_casting_functions := ''
|
|
||||||
sb.writeln('// NR methods = $t.gen_types.len')
|
|
||||||
for i, gen_type in t.gen_types {
|
|
||||||
// ptr_ctype can be for example Cat OR Cat_ptr:
|
|
||||||
ptr_ctype := gen_type.replace('*', '_ptr')
|
|
||||||
// cctype is the Cleaned Concrete Type name, *without ptr*,
|
|
||||||
// i.e. cctype is always just Cat, not Cat_ptr:
|
|
||||||
cctype := gen_type.replace('*', '')
|
|
||||||
|
|
||||||
// Speaker_Cat_index = 0
|
|
||||||
interface_index_name := '_${interface_name}_${ptr_ctype}_index'
|
|
||||||
|
|
||||||
generated_casting_functions += '
|
|
||||||
${interface_name} I_${cctype}_to_${interface_name}(${cctype} x) {
|
|
||||||
return (${interface_name}){
|
|
||||||
._object = (void*) memdup(&x, sizeof(${cctype})),
|
|
||||||
._interface_idx = ${interface_index_name} };
|
|
||||||
}
|
|
||||||
'
|
|
||||||
methods += '{\n'
|
|
||||||
for j, method in t.methods {
|
|
||||||
// Cat_speak
|
|
||||||
methods += ' (void*) ${cctype}_${method.name}'
|
|
||||||
if j < t.methods.len - 1 {
|
|
||||||
methods += ', \n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
methods += '\n},\n\n'
|
|
||||||
sb.writeln('int ${interface_index_name} = $i;')
|
|
||||||
}
|
|
||||||
if t.gen_types.len > 0 {
|
|
||||||
// methods = '{TCCSKIP(0)}'
|
|
||||||
// }
|
|
||||||
sb.writeln('void* (* ${interface_name}_name_table[][$t.methods.len]) = ' + '{ \n $methods \n }; ')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// The line below is needed so that C compilation succeeds,
|
|
||||||
// even if no interface methods are called.
|
|
||||||
// See https://github.com/zenith391/vgtk3/issues/7
|
|
||||||
sb.writeln('void* (* ${interface_name}_name_table[][1]) = ' + '{ {NULL} }; ')
|
|
||||||
}
|
|
||||||
if generated_casting_functions.len > 0 {
|
|
||||||
sb.writeln('// Casting functions for interface "${interface_name}" :')
|
|
||||||
sb.writeln( generated_casting_functions )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.str()
|
|
||||||
}
|
|
|
@ -1,344 +0,0 @@
|
||||||
module compiler
|
|
||||||
|
|
||||||
const (
|
|
||||||
c_common_macros = '
|
|
||||||
|
|
||||||
#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_DECLARATION
|
|
||||||
#undef EMPTY_STRUCT_INITIALIZATION
|
|
||||||
#define EMPTY_STRUCT_DECLARATION char _dummy
|
|
||||||
#define EMPTY_STRUCT_INITIALIZATION 0
|
|
||||||
#undef EMPTY_ARRAY_OF_ELEMS
|
|
||||||
#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n])
|
|
||||||
#undef TCCSKIP
|
|
||||||
#define TCCSKIP(x)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// for __offset_of
|
|
||||||
#ifndef __offsetof
|
|
||||||
#define __offsetof(s,memb) \\
|
|
||||||
((size_t)((char *)&((s *)0)->memb - (char *)0))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define OPTION_CAST(x) (x)
|
|
||||||
|
|
||||||
#ifndef V64_PRINTFORMAT
|
|
||||||
#ifdef PRIx64
|
|
||||||
#define V64_PRINTFORMAT "0x%"PRIx64
|
|
||||||
#elif defined(__WIN32__)
|
|
||||||
#define V64_PRINTFORMAT "0x%I64x"
|
|
||||||
#elif defined(__LINUX__) && defined(__LP64__)
|
|
||||||
#define V64_PRINTFORMAT "0x%lx"
|
|
||||||
#else
|
|
||||||
#define V64_PRINTFORMAT "0x%llx"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
'
|
|
||||||
c_headers = '
|
|
||||||
|
|
||||||
//#include <inttypes.h> // int64_t etc
|
|
||||||
#include <stdio.h> // TODO remove all these includes, define all function signatures and types manually
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
//#include "fns.h"
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h> // for va_list
|
|
||||||
#include <string.h> // memcpy
|
|
||||||
|
|
||||||
#if INTPTR_MAX == INT32_MAX
|
|
||||||
#define TARGET_IS_32BIT 1
|
|
||||||
#elif INTPTR_MAX == INT64_MAX
|
|
||||||
#define TARGET_IS_64BIT 1
|
|
||||||
#else
|
|
||||||
#error "The environment is not 32 or 64-bit."
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
|
|
||||||
#define TARGET_ORDER_IS_BIG
|
|
||||||
#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_AMD64) || defined(_M_X64) || defined(_M_IX86)
|
|
||||||
#define TARGET_ORDER_IS_LITTLE
|
|
||||||
#else
|
|
||||||
#error "Unknown architecture endianness"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <locale.h> // tolower
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <unistd.h> // sleep
|
|
||||||
extern char **environ;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__CYGWIN__) && !defined(_WIN32)
|
|
||||||
#error Cygwin is not supported, please use MinGW or Visual Studio.
|
|
||||||
#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
|
|
||||||
|
|
||||||
#ifdef __OpenBSD__
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/resource.h>
|
|
||||||
#include <sys/wait.h> // os__wait uses wait on nix
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __NetBSD__
|
|
||||||
#include <sys/wait.h> // os__wait uses wait on nix
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __sun
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h> // os__wait uses wait on nix
|
|
||||||
#endif
|
|
||||||
|
|
||||||
$c_common_macros
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define WINVER 0x0600
|
|
||||||
#ifdef _WIN32_WINNT
|
|
||||||
#undef _WIN32_WINNT
|
|
||||||
#endif
|
|
||||||
#define _WIN32_WINNT 0x0600
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#define _UNICODE
|
|
||||||
#define UNICODE
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <io.h> // _waccess
|
|
||||||
#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)
|
|
||||||
|
|
||||||
#include <dbghelp.h>
|
|
||||||
#pragma comment(lib, "Dbghelp.lib")
|
|
||||||
|
|
||||||
extern wchar_t **_wenviron;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#else
|
|
||||||
#include <pthread.h>
|
|
||||||
#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)
|
|
||||||
|
|
||||||
// NB: macro_fXX_eq and macro_fXX_ne are NOT used
|
|
||||||
// in the generated C code. They are here just for
|
|
||||||
// completeness/testing.
|
|
||||||
|
|
||||||
#define macro_f64_eq(a, b) (a == b)
|
|
||||||
#define macro_f64_ne(a, b) (a != b)
|
|
||||||
#define macro_f64_lt(a, b) (a < b)
|
|
||||||
#define macro_f64_le(a, b) (a <= b)
|
|
||||||
#define macro_f64_gt(a, b) (a > b)
|
|
||||||
#define macro_f64_ge(a, b) (a >= b)
|
|
||||||
|
|
||||||
#define macro_f32_eq(a, b) (a == b)
|
|
||||||
#define macro_f32_ne(a, b) (a != b)
|
|
||||||
#define macro_f32_lt(a, b) (a < b)
|
|
||||||
#define macro_f32_le(a, b) (a <= b)
|
|
||||||
#define macro_f32_gt(a, b) (a > b)
|
|
||||||
#define macro_f32_ge(a, b) (a >= b)
|
|
||||||
|
|
||||||
//================================== GLOBALS =================================*/
|
|
||||||
byte g_str_buf[1024];
|
|
||||||
int load_so(byteptr);
|
|
||||||
void reload_so();
|
|
||||||
|
|
||||||
// ============== wyhash ==============
|
|
||||||
// Author: Wang Yi <godspeed_china@yeah.net>
|
|
||||||
#ifndef wyhash_version_4
|
|
||||||
#define wyhash_version_4
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#if defined(_MSC_VER) && defined(_M_X64)
|
|
||||||
#include <intrin.h>
|
|
||||||
#pragma intrinsic(_umul128)
|
|
||||||
#endif
|
|
||||||
const uint64_t _wyp0=0xa0761d6478bd642full, _wyp1=0xe7037ed1a0b428dbull, _wyp2=0x8ebc6af09c88c6e3ull, _wyp3=0x589965cc75374cc3ull, _wyp4=0x1d8e4e27c47d124full;
|
|
||||||
static inline uint64_t _wyrotr(uint64_t v, unsigned k) { return (v>>k)|(v<<(64-k)); }
|
|
||||||
static inline uint64_t _wymum(uint64_t A, uint64_t B) {
|
|
||||||
#ifdef WYHASH32
|
|
||||||
uint64_t hh=(A>>32)*(B>>32), hl=(A>>32)*(unsigned)B, lh=(unsigned)A*(B>>32), ll=(uint64_t)(unsigned)A*(unsigned)B;
|
|
||||||
return _wyrotr(hl,32)^_wyrotr(lh,32)^hh^ll;
|
|
||||||
#else
|
|
||||||
#ifdef __SIZEOF_INT128__
|
|
||||||
__uint128_t r=A; r*=B; return (r>>64)^r;
|
|
||||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
|
||||||
A=_umul128(A, B, &B); return A^B;
|
|
||||||
#else
|
|
||||||
uint64_t ha=A>>32, hb=B>>32, la=(uint32_t)A, lb=(uint32_t)B, hi, lo;
|
|
||||||
uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl;
|
|
||||||
lo=t+(rm1<<32); c+=lo<t;hi=rh+(rm0>>32)+(rm1>>32)+c; return hi^lo;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#ifndef WYHASH_LITTLE_ENDIAN
|
|
||||||
#if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
|
||||||
#define WYHASH_LITTLE_ENDIAN 1
|
|
||||||
#elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
|
||||||
#define WYHASH_LITTLE_ENDIAN 0
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#if(WYHASH_LITTLE_ENDIAN) || defined(__TINYC__)
|
|
||||||
static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v; }
|
|
||||||
static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return v; }
|
|
||||||
#else
|
|
||||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER)
|
|
||||||
static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v); }
|
|
||||||
static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return __builtin_bswap32(v); }
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);}
|
|
||||||
static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return _byteswap_ulong(v); }
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
static inline uint64_t _wyr3(const uint8_t *p, unsigned k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1]; }
|
|
||||||
static inline uint64_t wyhash(const void* key, uint64_t len, uint64_t seed) {
|
|
||||||
const uint8_t *p=(const uint8_t*)key; uint64_t i=len&63;
|
|
||||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER)
|
|
||||||
#define _like_(x) __builtin_expect(x,1)
|
|
||||||
#define _unlike_(x) __builtin_expect(x,0)
|
|
||||||
#else
|
|
||||||
#define _like_(x) (x)
|
|
||||||
#define _unlike_(x) (x)
|
|
||||||
#endif
|
|
||||||
if(_unlike_(!i)) { }
|
|
||||||
else if(_unlike_(i<4)) seed=_wymum(_wyr3(p,i)^seed^_wyp0,seed^_wyp1);
|
|
||||||
else if(_like_(i<=8)) seed=_wymum(_wyr4(p)^seed^_wyp0,_wyr4(p+i-4)^seed^_wyp1);
|
|
||||||
else if(_like_(i<=16)) seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+i-8)^seed^_wyp1);
|
|
||||||
else if(_like_(i<=24)) seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1)^_wymum(_wyr8(p+i-8)^seed^_wyp2,seed^_wyp3);
|
|
||||||
else if(_like_(i<=32)) seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1)^_wymum(_wyr8(p+16)^seed^_wyp2,_wyr8(p+i-8)^seed^_wyp3);
|
|
||||||
else{ seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1)^_wymum(_wyr8(p+16)^seed^_wyp2,_wyr8(p+24)^seed^_wyp3)^_wymum(_wyr8(p+i-32)^seed^_wyp1,_wyr8(p+i-24)^seed^_wyp2)^_wymum(_wyr8(p+i-16)^seed^_wyp3,_wyr8(p+i-8)^seed^_wyp0); }
|
|
||||||
if(_like_(i==len)) return _wymum(seed,len^_wyp4);
|
|
||||||
uint64_t see1=seed, see2=seed, see3=seed;
|
|
||||||
for(p+=i,i=len-i; _like_(i>=64); i-=64,p+=64) {
|
|
||||||
seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1); see1=_wymum(_wyr8(p+16)^see1^_wyp2,_wyr8(p+24)^see1^_wyp3);
|
|
||||||
see2=_wymum(_wyr8(p+32)^see2^_wyp1,_wyr8(p+40)^see2^_wyp2); see3=_wymum(_wyr8(p+48)^see3^_wyp3,_wyr8(p+56)^see3^_wyp0);
|
|
||||||
}
|
|
||||||
return _wymum(seed^see1^see2,see3^len^_wyp4);
|
|
||||||
}
|
|
||||||
static inline uint64_t wyhash64(uint64_t A, uint64_t B) { return _wymum(_wymum(A^_wyp0, B^_wyp1), _wyp2); }
|
|
||||||
static inline uint64_t wyrand(uint64_t *seed) { *seed+=_wyp0; return _wymum(*seed^_wyp1,*seed); }
|
|
||||||
static inline double wy2u01(uint64_t r) { const double _wynorm=1.0/(1ull<<52); return (r>>11)*_wynorm; }
|
|
||||||
static inline double wy2gau(uint64_t r) { const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0; }
|
|
||||||
static inline uint64_t fastest_hash(const void *key, size_t len, uint64_t seed) {
|
|
||||||
const uint8_t *p = (const uint8_t *)key;
|
|
||||||
return _like_(len >= 4) ? (_wyr4(p) + _wyr4(p + len - 4)) * (_wyr4(p + (len >> 1) - 2) ^ seed) : (_like_(len) ? _wyr3(p, len) * (_wyp0 ^ seed) : seed);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
'
|
|
||||||
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 bool = function() {}
|
|
||||||
var rune = function() {}
|
|
||||||
var map_string = function() {}
|
|
||||||
var map_int = function() {}
|
|
||||||
|
|
||||||
'
|
|
||||||
c_builtin_types = '
|
|
||||||
|
|
||||||
//#include <inttypes.h> // int64_t etc
|
|
||||||
//#include <stdint.h> // int64_t etc
|
|
||||||
|
|
||||||
//================================== 1TYPEDEFS ================================*/
|
|
||||||
|
|
||||||
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 char* charptr;
|
|
||||||
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 array array_u16;
|
|
||||||
typedef array array_u32;
|
|
||||||
typedef array array_u64;
|
|
||||||
typedef map map_int;
|
|
||||||
typedef map map_string;
|
|
||||||
#ifndef bool
|
|
||||||
typedef int bool;
|
|
||||||
#define true 1
|
|
||||||
#define false 0
|
|
||||||
#endif
|
|
||||||
'
|
|
||||||
bare_c_headers = '
|
|
||||||
|
|
||||||
$c_common_macros
|
|
||||||
|
|
||||||
#ifndef exit
|
|
||||||
#define exit(rc) sys_exit(rc)
|
|
||||||
void sys_exit (int);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
'
|
|
||||||
)
|
|
|
@ -1,317 +0,0 @@
|
||||||
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_or_error(s string) {
|
|
||||||
if p.pref.is_prod {
|
|
||||||
p.error(s)
|
|
||||||
} else {
|
|
||||||
p.warn(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if p.scanner.is_fmt {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 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.print_line_on_error && s.nlines > 0 {
|
|
||||||
context_start_line := imax(0, (s.line_nr - error_context_before))
|
|
||||||
context_end_line := imin(s.nlines - 1, (s.line_nr + error_context_after + 1))
|
|
||||||
for cline := context_start_line; cline < context_end_line; cline++ {
|
|
||||||
line := '${(cline+1):5d}| ' + s.line(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 {
|
|
||||||
verror_paths_override := os.getenv('VERROR_PATHS')
|
|
||||||
use_relative_paths := match verror_paths_override {
|
|
||||||
'relative'{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
'absolute'{
|
|
||||||
false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s.print_rel_paths_on_error}}
|
|
||||||
if use_relative_paths {
|
|
||||||
workdir := os.getwd() + os.path_separator
|
|
||||||
if s.file_path.starts_with(workdir) {
|
|
||||||
return s.file_path.replace(workdir, '')
|
|
||||||
}
|
|
||||||
return s.file_path
|
|
||||||
}
|
|
||||||
return os.real_path(s.file_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (s &Scanner) is_color_output_on() bool {
|
|
||||||
return s.print_colored_error && 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.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
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.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 ienv_default(ename string, idefault int) int {
|
|
||||||
es := os.getenv(ename)
|
|
||||||
if es.len == 0 { return idefault }
|
|
||||||
return es.int()
|
|
||||||
}
|
|
||||||
|
|
||||||
// print_current_tokens/1 pretty prints the current token context, like this:
|
|
||||||
// // Your label: tokens[ 32] = Token{ .line: 8, .pos: 93, .tok: 85 } = mut
|
|
||||||
// // Your label: tokens[> 33] = Token{ .line: 8, .pos: 95, .tok: 1 } = b
|
|
||||||
// // Your label: tokens[ 34] = Token{ .line: 8, .pos: 98, .tok: 31 } = :=
|
|
||||||
// It is useful while debugging the v compiler itself. > marks p.token_idx
|
|
||||||
fn (p &Parser) print_current_tokens(label string){
|
|
||||||
btokens := ienv_default('V_BTOKENS', 5)
|
|
||||||
atokens := ienv_default('V_ATOKENS', 5)
|
|
||||||
ctoken_idx := p.token_idx
|
|
||||||
stoken_idx := imax(0, ctoken_idx - btokens)
|
|
||||||
etoken_idx := imin( ctoken_idx + atokens + 1, p.tokens.len)
|
|
||||||
for i := stoken_idx; i < etoken_idx; i++ {
|
|
||||||
idx := if i == ctoken_idx {
|
|
||||||
'>${i:3d}'
|
|
||||||
} else {
|
|
||||||
' ${i:3d}'
|
|
||||||
}
|
|
||||||
eprintln('$label: tokens[$idx] = ' + p.tokens[ i ].detailed_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalized_error(s string) string {
|
|
||||||
mut res := s
|
|
||||||
if !res.contains('__') {
|
|
||||||
// `[]int` instead of `array_int`
|
|
||||||
res = res.replace('array_', '[]')
|
|
||||||
}
|
|
||||||
res = res.replace('__', '.')
|
|
||||||
res = res.replace('Option_', '?')
|
|
||||||
res = res.replace('main.', '')
|
|
||||||
res = res.replace('ptr_', '&')
|
|
||||||
res = res.replace('_dot_', '.')
|
|
||||||
if res.contains('_V_MulRet_') {
|
|
||||||
res = res.replace('_V_MulRet_', '(')
|
|
||||||
res = res.replace('_V_', ', ')
|
|
||||||
res = res[..res.len - 1] + ')"' //"// quote balance comment. do not remove
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub 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
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (s &Scanner) get_last_nl_from_pos(_pos int) int {
|
|
||||||
pos := if _pos >= s.text.len { s.text.len - 1 } else { _pos }
|
|
||||||
for i := pos; i >= 0; i-- {
|
|
||||||
if s.text[i] == `\n` {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (s &Scanner) get_scanner_pos_of_token(tok &Token) ScannerPos {
|
|
||||||
return ScannerPos{
|
|
||||||
pos: tok.pos
|
|
||||||
line_nr: tok.line_nr
|
|
||||||
last_nl_pos: s.get_last_nl_from_pos(tok.pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////
|
|
||||||
fn (p mut Parser) mutable_arg_error(i int, arg Var, f Fn) {
|
|
||||||
mut dots_example := 'mut $p.lit'
|
|
||||||
if i > 0 {
|
|
||||||
dots_example = '.., ' + dots_example
|
|
||||||
}
|
|
||||||
if i < f.args.len - 1 {
|
|
||||||
dots_example = dots_example + ',..'
|
|
||||||
}
|
|
||||||
p.error('`$arg.name` is a mutable argument, you need to provide `mut`: ' + '`$f.name ($dots_example)`')
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
warn_match_arrow = '=> is no longer needed in match statements, use\n' + 'match foo {
|
|
||||||
1 { bar }
|
|
||||||
2 { baz }
|
|
||||||
else { ... }
|
|
||||||
}'
|
|
||||||
// make_receiver_mutable =
|
|
||||||
err_used_as_value = 'used as value'
|
|
||||||
and_or_error = 'use `()` to make the boolean expression clear\n' + 'for example: `(a && b) || c` instead of `a && b || c`'
|
|
||||||
err_modify_bitfield = 'to modify a bitfield flag use the methods: set, clear, toggle. and to check for flag use: has'
|
|
||||||
)
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
pub fn get_v_options_and_main_command(args []string) ([]string,string) {
|
|
||||||
mut options := []string
|
|
||||||
mut potential_commands := []string
|
|
||||||
for i := 0; i < args.len; i++ {
|
|
||||||
a := args[i]
|
|
||||||
if !a.starts_with('-') {
|
|
||||||
potential_commands << a
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
options << a
|
|
||||||
if a in ['-o', '-os', '-cc', '-cflags', '-d'] {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// potential_commands[0] is always the executable itself, so ignore it
|
|
||||||
command := if potential_commands.len > 1 { potential_commands[1] } else { '' }
|
|
||||||
return options,command
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,612 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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 {
|
|
||||||
os := os_from_string(name)
|
|
||||||
ifdef_name := os_name_to_ifdef(name)
|
|
||||||
if name == 'mac' {
|
|
||||||
p.warn('use `macos` instead of `mac`')
|
|
||||||
}
|
|
||||||
|
|
||||||
if not {
|
|
||||||
if name == 'linux_or_macos' {
|
|
||||||
p.genln('#if !defined(__linux__) && !defined(__APPLE__)')
|
|
||||||
} else {
|
|
||||||
p.genln('#ifndef $ifdef_name')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if name == 'linux_or_macos' {
|
|
||||||
p.genln('#if defined(__linux__) || defined(__APPLE__)')
|
|
||||||
} else {
|
|
||||||
p.genln('#ifdef $ifdef_name')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.check(.lcbr)
|
|
||||||
if ((!not && os != p.os) || (not && os == p.os)) && !name.contains('_or_') &&
|
|
||||||
!p.scanner.is_fmt && !p.pref.output_cross_c {
|
|
||||||
// `$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 == .key_return {
|
|
||||||
p.returns = true
|
|
||||||
}
|
|
||||||
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 == 'x64' {
|
|
||||||
p.comptime_if_block('TARGET_IS_64BIT', not)
|
|
||||||
}
|
|
||||||
else if name == 'x32' {
|
|
||||||
p.comptime_if_block('TARGET_IS_32BIT', not)
|
|
||||||
}
|
|
||||||
else if name == 'big_endian' {
|
|
||||||
p.comptime_if_block('TARGET_ORDER_IS_BIG', not)
|
|
||||||
}
|
|
||||||
else if name == 'little_endian' {
|
|
||||||
p.comptime_if_block('TARGET_ORDER_IS_LITTLE', not)
|
|
||||||
}
|
|
||||||
else if name == 'debug' {
|
|
||||||
p.comptime_if_block('VDEBUG', not)
|
|
||||||
}
|
|
||||||
else if name == 'prealloc' {
|
|
||||||
p.comptime_if_block('VPREALLOC', not)
|
|
||||||
}
|
|
||||||
else if name == 'tinyc' {
|
|
||||||
p.comptime_if_block('__TINYC__', not)
|
|
||||||
}
|
|
||||||
else if name == 'glibc' {
|
|
||||||
p.comptime_if_block('__GLIBC__', not)
|
|
||||||
}
|
|
||||||
else if name == 'mingw' {
|
|
||||||
p.comptime_if_block('__MINGW32__', not)
|
|
||||||
}
|
|
||||||
else if name == 'msvc' {
|
|
||||||
p.comptime_if_block('_MSC_VER', not)
|
|
||||||
}
|
|
||||||
else if name == 'clang' {
|
|
||||||
p.comptime_if_block('__clang__', not)
|
|
||||||
}
|
|
||||||
else if p.v.pref.compile_defines_all.len > 0 && name in p.v.pref.compile_defines_all {
|
|
||||||
// Support for *optional* custom compile defines, i.e.:
|
|
||||||
//
|
|
||||||
// `[if custom]` => custom should be defined
|
|
||||||
// `$if custom { // stuff }` => custom should be defined
|
|
||||||
// `$if custom ? { // stuff }` => custom may not be defined
|
|
||||||
//
|
|
||||||
// Custom compile defines are given on the CLI, like this:
|
|
||||||
// `v -d custom=0` => means that the custom will be defined,
|
|
||||||
// but that it will be considered false.
|
|
||||||
// `v -d custom=1`, which is equivalent to `v -d custom`,
|
|
||||||
// means that the custom will be defined, and considered true.
|
|
||||||
//
|
|
||||||
// The ? sign, means that `custom` is optional, and when
|
|
||||||
// it is not present at all at the command line, then the
|
|
||||||
// block will just be ignored, instead of erroring.
|
|
||||||
if p.tok == .question {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
p.comptime_if_block('CUSTOM_DEFINE_${name}', not)
|
|
||||||
} else {
|
|
||||||
if p.tok == .question {
|
|
||||||
p.next()
|
|
||||||
p.comptime_if_block('CUSTOM_DEFINE_${name}', not)
|
|
||||||
}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.fspace()
|
|
||||||
p.next()
|
|
||||||
p.next()
|
|
||||||
p.fspace() // spaces before and after $else
|
|
||||||
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_else {
|
|
||||||
p.error('use `$' + 'else` instead of `else` in comptime if statements')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
_,val := p.tmp_expr()
|
|
||||||
// p.bool_expression()
|
|
||||||
// val := p.cgen.end_tmp()
|
|
||||||
p.check(.rcbr)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
else if p.tok == .name && p.lit == 'vweb' {
|
|
||||||
// $vweb.html()
|
|
||||||
// Compile vweb html template to V code, parse that V code and embed the resulting V functions
|
|
||||||
// that returns an html string
|
|
||||||
mut path := p.cur_fn.name + '.html'
|
|
||||||
if p.pref.is_debug {
|
|
||||||
println('>>> compiling vweb HTML template "$path"')
|
|
||||||
}
|
|
||||||
if !os.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.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 p.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println('\n\n')
|
|
||||||
println('>>> vweb template for ${path}:')
|
|
||||||
println(v_code)
|
|
||||||
println('>>> vweb template END')
|
|
||||||
println('\n\n')
|
|
||||||
}
|
|
||||||
is_strings_imorted := p.import_table.known_import('strings')
|
|
||||||
if !is_strings_imorted {
|
|
||||||
p.register_import('strings', 0) // used by v_code
|
|
||||||
}
|
|
||||||
p.import_table.register_used_import('strings')
|
|
||||||
p.genln('/////////////////// tmpl start')
|
|
||||||
p.statements_from_text(v_code, false, path)
|
|
||||||
p.genln('/////////////////// tmpl end')
|
|
||||||
receiver := p.cur_fn.args[0]
|
|
||||||
dot := if receiver.is_mut || receiver.ptr || receiver.typ.ends_with('*') { '->' } else { '.' }
|
|
||||||
p.genln('vweb__Context_html( & $receiver.name /*!*/$dot vweb, tmpl_res)')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.error('bad comp_time expression')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #include, #flag, #v
|
|
||||||
fn (p mut Parser) chash() {
|
|
||||||
hash := p.lit.trim_space()
|
|
||||||
// println('chsh() file=$p.file hash="$hash"')
|
|
||||||
p.next()
|
|
||||||
p.fgen_nl()
|
|
||||||
if hash.starts_with('flag ') {
|
|
||||||
if p.first_pass() {
|
|
||||||
mut flag := hash[5..]
|
|
||||||
// expand `@VROOT` to its absolute path
|
|
||||||
if flag.contains('@VROOT') {
|
|
||||||
vmod_file_location := p.v.mod_file_cacher.get( p.file_path_dir )
|
|
||||||
if vmod_file_location.vmod_file.len == 0 {
|
|
||||||
// There was no actual v.mod file found.
|
|
||||||
p.error_with_token_index('To use @VROOT, you need' +
|
|
||||||
' to have a "v.mod" file in ${p.file_path_dir},' +
|
|
||||||
' or in one of its parent folders.',
|
|
||||||
p.cur_tok_index() - 1)
|
|
||||||
}
|
|
||||||
flag = flag.replace('@VROOT', vmod_file_location.vmod_folder )
|
|
||||||
}
|
|
||||||
for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] {
|
|
||||||
if flag.contains(deprecated) {
|
|
||||||
p.error('${deprecated} had been deprecated, use @VROOT instead.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// p.log('adding flag "$flag"')
|
|
||||||
_ = p.table.parse_cflag(flag, p.mod, p.v.pref.compile_defines_all ) or {
|
|
||||||
p.error_with_token_index(err, p.cur_tok_index() - 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if hash.starts_with('include') {
|
|
||||||
if p.first_pass() && !p.is_vh {
|
|
||||||
/*
|
|
||||||
if !p.pref.building_v && !p.fileis('vlib') {
|
|
||||||
p.warn('C #includes will soon be removed from the language' +
|
|
||||||
'\ndefine the C structs and functions in V')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
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') or {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file := hash[pos + 5..]
|
|
||||||
// if p.pref.build_mode != .default_mode {
|
|
||||||
p.genln('#include $file')
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
else if hash.contains('define') {
|
|
||||||
// Move defines on top
|
|
||||||
if p.first_pass() {
|
|
||||||
p.cgen.includes << '#$hash'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// // 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"')
|
|
||||||
if hash.starts_with('include') {
|
|
||||||
println('include')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
mut j := 0
|
|
||||||
for method in typ.methods {
|
|
||||||
if method.typ != 'void' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
receiver := method.args[0]
|
|
||||||
if !p.expr_var.ptr {
|
|
||||||
p.error('`$p.expr_var.name` needs to be a reference')
|
|
||||||
}
|
|
||||||
amp := if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' }
|
|
||||||
if j > 0 {
|
|
||||||
p.gen(' else ')
|
|
||||||
}
|
|
||||||
p.genln('if ( string_eq($var, _STR("$method.name")) ) ' + '${typ.name}_$method.name ($amp $p.expr_var.name);')
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
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_default_str_method_if_missing(typename string) (bool, string) {
|
|
||||||
// NB: string_type_name can be != typename, if the base typename has str()
|
|
||||||
mut string_type_name := typename
|
|
||||||
typ := p.table.find_type(typename)
|
|
||||||
is_varg := typename.starts_with('varg_')
|
|
||||||
is_array := typename.starts_with('array_')
|
|
||||||
is_struct := typ.cat == .struct_
|
|
||||||
mut has_str_method := p.table.type_has_method(typ, 'str')
|
|
||||||
if !has_str_method {
|
|
||||||
if is_varg {
|
|
||||||
p.gen_varg_str(typ)
|
|
||||||
has_str_method = true
|
|
||||||
}
|
|
||||||
else if is_array {
|
|
||||||
p.gen_array_str(typ)
|
|
||||||
has_str_method = true
|
|
||||||
}
|
|
||||||
else if is_struct {
|
|
||||||
p.gen_struct_str(typ)
|
|
||||||
has_str_method = true
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
btypename := p.base_type(typ.name)
|
|
||||||
if btypename != typ.name {
|
|
||||||
base_type := p.find_type(btypename)
|
|
||||||
if base_type.has_method('str'){
|
|
||||||
string_type_name = base_type.name
|
|
||||||
has_str_method = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return has_str_method, string_type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_array_str(typ Type) {
|
|
||||||
if typ.has_method('str') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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 := parse_pointer(typ.name[6..])
|
|
||||||
elm_type2 := p.table.find_type(elm_type)
|
|
||||||
is_array := elm_type.starts_with('array_')
|
|
||||||
if is_array {
|
|
||||||
p.gen_array_str(elm_type2)
|
|
||||||
}
|
|
||||||
else if p.typ_to_fmt(elm_type, 0) == '' && !p.table.type_has_method(elm_type2, 'str') {
|
|
||||||
has_str_method, _ := p.gen_default_str_method_if_missing( elm_type )
|
|
||||||
if !has_str_method {
|
|
||||||
p.error('cant print []${elm_type}, unhandled print of ${elm_type}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.v.vgen_buf.writeln('
|
|
||||||
pub 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' }` => interpolated to string 'Foo { 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('pub fn (a $typ.name) str() string {\nreturn')
|
|
||||||
short_struct_name := typ.name.all_after('__')
|
|
||||||
sb.writeln("'$short_struct_name {")
|
|
||||||
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 parsed by V after the main pass.
|
|
||||||
p.cgen.fns << 'string ${typ.name}_str();'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_varg_str(typ Type) {
|
|
||||||
elm_type := typ.name[5..]
|
|
||||||
elm_type2 := p.table.find_type(elm_type)
|
|
||||||
is_array := elm_type.starts_with('array_')
|
|
||||||
if is_array {
|
|
||||||
p.gen_array_str(elm_type2)
|
|
||||||
}
|
|
||||||
else if elm_type2.cat == .struct_ {
|
|
||||||
p.gen_struct_str(elm_type2)
|
|
||||||
}
|
|
||||||
p.v.vgen_buf.writeln('
|
|
||||||
pub 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();'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_array_filter(str_typ string, method_ph int) {
|
|
||||||
/*
|
|
||||||
// V
|
|
||||||
a := [1,2,3,4]
|
|
||||||
b := a.filter(it % 2 == 0)
|
|
||||||
|
|
||||||
// C
|
|
||||||
array_int a = ...;
|
|
||||||
array_int tmp2 = new_array(0, 4, 4);
|
|
||||||
for (int i = 0; i < a.len; i++) {
|
|
||||||
int it = ((int*)a.data)[i];
|
|
||||||
if (it % 2 == 0) array_push(&tmp2, &it);
|
|
||||||
}
|
|
||||||
array_int b = tmp2;
|
|
||||||
*/
|
|
||||||
val_type := parse_pointer(str_typ[6..])
|
|
||||||
p.open_scope()
|
|
||||||
p.register_var(Var{
|
|
||||||
name: 'it'
|
|
||||||
typ: val_type
|
|
||||||
})
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
p.cgen.resetln('')
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
a := p.expr_var.name
|
|
||||||
p.cgen.set_placeholder(method_ph, '\n$str_typ $tmp = new_array(0, $a .len,sizeof($val_type));\n')
|
|
||||||
p.genln('for (int i = 0; i < ${a}.len; i++) {')
|
|
||||||
p.genln('$val_type it = (($val_type*)${a}.data)[i];')
|
|
||||||
p.gen('if (')
|
|
||||||
p.bool_expression()
|
|
||||||
p.genln(') array_push(&$tmp, &it);')
|
|
||||||
// p.genln(') array_push(&$tmp, &((($val_type*)${a}.data)[i]));')
|
|
||||||
// p.genln(') array_push(&$tmp, ${a}.data + i * ${a}.element_size);')
|
|
||||||
p.genln('}')
|
|
||||||
p.gen(tmp) // TODO why does this `gen()` work?
|
|
||||||
p.check(.rpar)
|
|
||||||
p.close_scope()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_array_map(str_typ string, method_ph int) string {
|
|
||||||
/*
|
|
||||||
// V
|
|
||||||
a := [1,2,3,4]
|
|
||||||
b := a.map(it * 2)
|
|
||||||
|
|
||||||
// C
|
|
||||||
array_int a = ...;
|
|
||||||
array_int tmp2 = new_array(0, 4, 4);
|
|
||||||
for (int i = 0; i < a.len; i++) {
|
|
||||||
int it = ((int*)a.data)[i];
|
|
||||||
_PUSH(tmp2, it * 2, tmp3, int)
|
|
||||||
}
|
|
||||||
array_int b = tmp2;
|
|
||||||
*/
|
|
||||||
val_type := parse_pointer(str_typ[6..])
|
|
||||||
p.open_scope()
|
|
||||||
p.register_var(Var{
|
|
||||||
name: 'it'
|
|
||||||
typ: val_type
|
|
||||||
})
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
p.cgen.resetln('')
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
tmp_elm := p.get_tmp()
|
|
||||||
a := p.expr_var.name
|
|
||||||
map_type,expr := p.tmp_expr()
|
|
||||||
p.cgen.set_placeholder(method_ph, '\narray $tmp = new_array(0, $a .len, ' + 'sizeof($map_type));\n')
|
|
||||||
p.genln('for (int i = 0; i < ${a}.len; i++) {')
|
|
||||||
p.genln('$val_type it = (($val_type*)${a}.data)[i];')
|
|
||||||
p.genln('_PUSH(&$tmp, $expr, $tmp_elm, $map_type)')
|
|
||||||
p.genln('}')
|
|
||||||
p.gen(tmp) // TODO why does this `gen()` work?
|
|
||||||
p.check(.rpar)
|
|
||||||
p.close_scope()
|
|
||||||
return 'array_' + stringify_pointer(map_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) comptime_if_block(name string, not bool) {
|
|
||||||
if not {
|
|
||||||
p.genln('#ifndef $name')
|
|
||||||
}else{
|
|
||||||
p.genln('#ifdef $name')
|
|
||||||
}
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.statements_no_rcbr()
|
|
||||||
if !(p.tok == .dollar && p.peek() == .key_else) {
|
|
||||||
p.genln('#endif')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_enum_flag_methods(typ mut Type) {
|
|
||||||
for method in ['set', 'clear', 'toggle', 'has'] {
|
|
||||||
typ.methods << Fn{
|
|
||||||
name: method
|
|
||||||
typ: if method == 'has' { 'bool' } else { 'void' }
|
|
||||||
args: [Var{
|
|
||||||
typ: typ.name
|
|
||||||
is_mut: true
|
|
||||||
is_arg: true
|
|
||||||
}, Var{
|
|
||||||
typ: typ.name
|
|
||||||
is_arg: true
|
|
||||||
}]
|
|
||||||
is_method: true
|
|
||||||
is_public: true
|
|
||||||
receiver_typ: typ.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.v.vgen_buf.writeln('
|
|
||||||
pub fn (e mut $typ.name) set(flag $typ.name) { *e = int(*e) | (1 << int(flag)) }
|
|
||||||
pub fn (e mut $typ.name) clear(flag $typ.name) { *e = int(*e) &~ (1 << int(flag)) }
|
|
||||||
pub fn (e mut $typ.name) toggle(flag $typ.name) { *e = int(*e) ^ (1 << int(flag)) }
|
|
||||||
pub fn (e &$typ.name) has(flag $typ.name) bool { return int(*e)&(1 << int(flag)) != 0 }')
|
|
||||||
p.cgen.fns << 'void ${typ.name}_set($typ.name *e, $typ.name flag);'
|
|
||||||
p.cgen.fns << 'void ${typ.name}_clear($typ.name *e, $typ.name flag);'
|
|
||||||
p.cgen.fns << 'void ${typ.name}_toggle($typ.name *e, $typ.name flag);'
|
|
||||||
p.cgen.fns << 'bool ${typ.name}_has($typ.name *e, $typ.name flag);'
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DepGraph {
|
|
||||||
pub mut:
|
|
||||||
acyclic bool
|
|
||||||
nodes []DepGraphNode
|
|
||||||
}
|
|
||||||
|
|
||||||
struct OrderedDepMap {
|
|
||||||
mut:
|
|
||||||
keys []string
|
|
||||||
data map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (o mut OrderedDepMap) set(name string, deps []string) {
|
|
||||||
if !(name in o.data) {
|
|
||||||
o.keys << name
|
|
||||||
}
|
|
||||||
o.data[name] = deps
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (o mut OrderedDepMap) add(name string, deps []string) {
|
|
||||||
mut d := o.data[name]
|
|
||||||
for dep in deps {
|
|
||||||
if !(dep in d) {
|
|
||||||
d << dep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.set(name, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (o &OrderedDepMap) get(name string) []string {
|
|
||||||
return o.data[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (o mut OrderedDepMap) delete(name string) {
|
|
||||||
if !(name in o.data) {
|
|
||||||
panic('delete: no such key: $name')
|
|
||||||
}
|
|
||||||
for i, _ in o.keys {
|
|
||||||
if o.keys[i] == name {
|
|
||||||
o.keys.delete(i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.data.delete(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (o mut OrderedDepMap) apply_diff(name string, deps []string) {
|
|
||||||
mut diff := []string
|
|
||||||
for dep in o.data[name] {
|
|
||||||
if !(dep in deps) {
|
|
||||||
diff << dep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.set(name, diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (o &OrderedDepMap) size() int {
|
|
||||||
return o.data.size
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := OrderedDepMap{}
|
|
||||||
for node in graph.nodes {
|
|
||||||
node_names.add(node.name, node.deps)
|
|
||||||
}
|
|
||||||
mut node_deps := node_names
|
|
||||||
mut resolved := new_dep_graph()
|
|
||||||
for node_deps.size() != 0 {
|
|
||||||
mut ready_set := []string
|
|
||||||
for name in node_deps.keys {
|
|
||||||
deps := node_deps.data[name]
|
|
||||||
if deps.len == 0 {
|
|
||||||
ready_set << name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ready_set.len == 0 {
|
|
||||||
mut g := new_dep_graph()
|
|
||||||
g.acyclic = false
|
|
||||||
for name in node_deps.keys {
|
|
||||||
g.add(name, node_names.data[name])
|
|
||||||
}
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
for name in ready_set {
|
|
||||||
node_deps.delete(name)
|
|
||||||
resolved.add(name, node_names.data[name])
|
|
||||||
}
|
|
||||||
for name in node_deps.keys {
|
|
||||||
node_deps.apply_diff(name, ready_set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolved
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (graph &DepGraph) last_node() DepGraphNode {
|
|
||||||
return graph.nodes[graph.nodes.len - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (graph &DepGraph) display() string {
|
|
||||||
mut out := '\n'
|
|
||||||
for node in graph.nodes {
|
|
||||||
for dep in node.deps {
|
|
||||||
out += ' * $node.name -> $dep\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (graph &DepGraph) display_cycles() string {
|
|
||||||
mut node_names := map[string]DepGraphNode
|
|
||||||
for node in graph.nodes {
|
|
||||||
node_names[node.name] = node
|
|
||||||
}
|
|
||||||
mut out := '\n'
|
|
||||||
for node in graph.nodes {
|
|
||||||
for dep in node.deps {
|
|
||||||
if !(dep in node_names) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dn := node_names[dep]
|
|
||||||
if node.name in dn.deps {
|
|
||||||
out += ' * $node.name -> $dep\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
fn (p mut Parser) enum_decl(no_name bool) {
|
|
||||||
is_pub := p.tok == .key_pub
|
|
||||||
if is_pub {
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
p.check(.key_enum)
|
|
||||||
p.fspace()
|
|
||||||
mut enum_name := p.check_name()
|
|
||||||
is_c := enum_name == 'C' && p.tok == .dot
|
|
||||||
if is_c {
|
|
||||||
p.check(.dot)
|
|
||||||
enum_name = p.check_name()
|
|
||||||
}
|
|
||||||
// Specify full type name
|
|
||||||
if !p.builtin_mod && p.mod != 'main' {
|
|
||||||
enum_name = p.prepend_mod(enum_name)
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
mut val := 0
|
|
||||||
mut fields := []string
|
|
||||||
mut tuple_variants := []string
|
|
||||||
for p.tok == .name {
|
|
||||||
field := p.check_name()
|
|
||||||
if p.pass == .decl && p.tok != .lpar && contains_capital(field) {
|
|
||||||
p.warn('enum values cannot contain uppercase letters, use snake_case instead (`$field`)')
|
|
||||||
}
|
|
||||||
fields << field
|
|
||||||
name := '${mod_gen_name(p.mod)}__${enum_name}_$field'
|
|
||||||
if p.tok == .assign {
|
|
||||||
p.fspace()
|
|
||||||
mut enum_assign_tidx := p.cur_tok_index()
|
|
||||||
next := p.peek()
|
|
||||||
if next in [.number, .minus] {
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
is_neg := p.tok == .minus
|
|
||||||
if is_neg {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
val = p.lit.int()
|
|
||||||
if is_neg {
|
|
||||||
val = -val
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.next()
|
|
||||||
enum_assign_tidx = p.cur_tok_index()
|
|
||||||
p.error_with_token_index('only numbers are allowed in enum initializations', enum_assign_tidx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `BoolExpr(bool)`
|
|
||||||
else if p.tok == .lpar {
|
|
||||||
if !field[0].is_capital() {
|
|
||||||
p.error('sum types must be capitalized')
|
|
||||||
}
|
|
||||||
p.check(.lpar)
|
|
||||||
tuple_variants << p.get_type()
|
|
||||||
p.check(.rpar)
|
|
||||||
if p.pass == .main {
|
|
||||||
p.cgen.consts << '#define ${field}_type $val // LOL'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.pass == .main {
|
|
||||||
p.cgen.consts << '#define $name $val'
|
|
||||||
}
|
|
||||||
if p.tok == .comma {
|
|
||||||
p.next()
|
|
||||||
p.fremove_last()
|
|
||||||
}
|
|
||||||
p.fgen_nl()
|
|
||||||
val++
|
|
||||||
}
|
|
||||||
is_flag := p.attr == 'flag'
|
|
||||||
if is_flag && fields.len > 32 {
|
|
||||||
p.error('when an enum is used as bit field, it must have a max of 32 fields')
|
|
||||||
}
|
|
||||||
mut T := Type{
|
|
||||||
name: enum_name
|
|
||||||
mod: p.mod
|
|
||||||
parent: 'int'
|
|
||||||
cat: .enum_
|
|
||||||
enum_vals: fields.clone()
|
|
||||||
is_public: is_pub
|
|
||||||
is_flag: is_flag
|
|
||||||
}
|
|
||||||
p.table.tuple_variants[enum_name] = tuple_variants
|
|
||||||
if is_flag && !p.first_pass() {
|
|
||||||
p.gen_enum_flag_methods(mut T)
|
|
||||||
}
|
|
||||||
if p.pass == .decl || is_flag {
|
|
||||||
p.table.register_type(T)
|
|
||||||
}
|
|
||||||
// Register `Expression` enum
|
|
||||||
if tuple_variants.len > 0 && p.pass == .main {
|
|
||||||
p.cgen.typedefs << 'typedef struct {
|
|
||||||
void* obj;
|
|
||||||
int typ;
|
|
||||||
} $enum_name;
|
|
||||||
'
|
|
||||||
}
|
|
||||||
// Skip nameless enums
|
|
||||||
else if !no_name && !p.first_pass() {
|
|
||||||
p.cgen.typedefs << 'typedef int $enum_name;'
|
|
||||||
}
|
|
||||||
p.check(.rcbr)
|
|
||||||
p.fgen_nl()
|
|
||||||
p.fgen_nl()
|
|
||||||
if !no_name && fields.len == 0 {
|
|
||||||
p.error('Empty enums are not allowed.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) check_enum_member_access() {
|
|
||||||
if p.expected_type.starts_with('Option_') {
|
|
||||||
p.expected_type = p.expected_type[7..]
|
|
||||||
}
|
|
||||||
tt := p.find_type(p.expected_type)
|
|
||||||
if tt.cat == .enum_ {
|
|
||||||
p.check(.dot)
|
|
||||||
val := p.check_name()
|
|
||||||
// Make sure this enum value exists
|
|
||||||
if !tt.has_enum_val(val) {
|
|
||||||
p.error('enum `$tt.name` does not have value `$val`')
|
|
||||||
}
|
|
||||||
p.gen(mod_gen_name(tt.mod) + '__' + p.expected_type + '_' + val)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.error('`$tt.name` is not an enum')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
enum Expression {
|
|
||||||
Boolean(bool),
|
|
||||||
Integer(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let expr = Expression::Integer(10);
|
|
||||||
let mut val = Expression::Boolean(true);
|
|
||||||
val = expr;
|
|
||||||
match val {
|
|
||||||
Expression::Integer(n) => println!("INT {}", n),
|
|
||||||
Expression::Boolean(b) => println!("BOOL {}", b),
|
|
||||||
}
|
|
||||||
|
|
||||||
//println!("HELLO {}", val);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,958 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
fn (p mut Parser) bool_expression() string {
|
|
||||||
//is_ret := p.prev_tok == .key_return
|
|
||||||
start_ph := p.cgen.add_placeholder()
|
|
||||||
mut expected := p.expected_type
|
|
||||||
tok := p.tok
|
|
||||||
typ := p.bterm()
|
|
||||||
mut got_and := false // to catch `a && b || c` in one expression without ()
|
|
||||||
mut got_or := false
|
|
||||||
for p.tok == .and || p.tok == .logical_or {
|
|
||||||
if p.tok == .and {
|
|
||||||
got_and = true
|
|
||||||
if got_or {
|
|
||||||
p.error(and_or_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.tok == .logical_or {
|
|
||||||
got_or = true
|
|
||||||
if got_and {
|
|
||||||
p.error(and_or_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.is_sql {
|
|
||||||
if p.tok == .and {
|
|
||||||
p.gen(' and ')
|
|
||||||
}
|
|
||||||
else if p.tok == .logical_or {
|
|
||||||
p.gen(' or ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen(' ${p.tok.str()} ')
|
|
||||||
}
|
|
||||||
p.check_space(p.tok)
|
|
||||||
p.check_types(p.bterm(), typ)
|
|
||||||
if typ != 'bool' {
|
|
||||||
p.error('logical operators `&&` and `||` require booleans')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ == '' {
|
|
||||||
println('curline:')
|
|
||||||
println(p.cgen.cur_line)
|
|
||||||
println(tok.str())
|
|
||||||
p.error('expr() returns empty type')
|
|
||||||
}
|
|
||||||
if p.inside_return_expr && p.expected_type.contains('_MulRet_') { //is_ret { // return a,b hack TODO
|
|
||||||
expected = p.expected_type
|
|
||||||
}
|
|
||||||
// `window.widget = button`, widget is an interface
|
|
||||||
if expected != typ && expected.ends_with('er') && expected.contains('I') {
|
|
||||||
tt := typ.replace('*', '_ptr')
|
|
||||||
/*
|
|
||||||
if p.fileis('button') || p.fileis('textbox') {
|
|
||||||
p.warn('exp="$expected" typ="$typ" tt="$tt"')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
p.cgen.set_placeholder(start_ph,
|
|
||||||
'($expected) { ._interface_idx = /* :) */ _${expected}_${tt}_index, ._object = ' )
|
|
||||||
p.gen('}')
|
|
||||||
//p.satisfies_interface(expected, typ, true)
|
|
||||||
}
|
|
||||||
// e.g. `return InfixExpr{}` in a function expecting `Expr`
|
|
||||||
if expected != typ && expected in p.table.sum_types { // TODO perf
|
|
||||||
//p.warn('SUM CAST exp=$expected typ=$typ p.exp=$p.expected_type')
|
|
||||||
if typ in p.table.sum_types[expected] {
|
|
||||||
p.cgen.set_placeholder(start_ph, '/*SUM TYPE CAST2*/ ($expected) { .obj = memdup( &($typ[]) { ')
|
|
||||||
tt := typ.all_after('_') // TODO
|
|
||||||
p.gen('}, sizeof($typ) ), .typ = SumType_${expected}_${tt} }')//${val}_type }')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `as` cast
|
|
||||||
// TODO remove copypasta
|
|
||||||
if p.tok == .key_as {
|
|
||||||
return p.key_as(typ, start_ph)
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) key_as(typ string, start_ph int) string {
|
|
||||||
p.fspace()
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
cast_typ := p.get_type()
|
|
||||||
if typ == cast_typ {
|
|
||||||
p.error('casting `$typ` to `$cast_typ` is not needed')
|
|
||||||
}
|
|
||||||
if typ in p.table.sum_types {
|
|
||||||
if !(cast_typ in p.table.sum_types[typ]) {
|
|
||||||
p.error('cannot cast `$typ` to `$cast_typ`. `$cast_typ` is not a variant of `$typ`')
|
|
||||||
}
|
|
||||||
p.cgen.set_placeholder(start_ph, '*($cast_typ*)')
|
|
||||||
p.gen('.obj')
|
|
||||||
// Make sure the sum type can be cast, otherwise throw a runtime error
|
|
||||||
/*
|
|
||||||
sum_type:= p.cgen.cur_line.all_after('*) (').replace('.obj', '.typ')
|
|
||||||
|
|
||||||
n := cast_typ.all_after('__')
|
|
||||||
p.cgen.insert_before('if (($sum_type != SumType_${typ}_$n) {
|
|
||||||
puts("runtime error: $p.file_name:$p.scanner.line_nr cannot cast sum type `$typ` to `$n`");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
')
|
|
||||||
*/
|
|
||||||
} else {
|
|
||||||
p.error('`as` casts have been removed, use the old syntax: `Type(val)`')
|
|
||||||
}
|
|
||||||
return cast_typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) bterm() string {
|
|
||||||
ph := p.cgen.add_placeholder()
|
|
||||||
mut typ := p.expression()
|
|
||||||
p.expected_type = typ
|
|
||||||
is_str := typ == 'string' && !p.is_sql
|
|
||||||
is_ustr := typ == 'ustring'
|
|
||||||
base := p.base_type(typ)
|
|
||||||
is_float := base[0] == `f` && (base in ['f64', 'f32']) && !(p.cur_fn.name in ['f64_abs', 'f32_abs']) && p.cur_fn.name != 'eq'
|
|
||||||
is_array := typ.starts_with('array_')
|
|
||||||
expr_type := base
|
|
||||||
tok := p.tok
|
|
||||||
/*
|
|
||||||
if tok == .assign {
|
|
||||||
p.error('no = ')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if tok in [.eq, .gt, .lt, .le, .ge, .ne] {
|
|
||||||
// TODO: remove when array comparing is supported
|
|
||||||
if is_array {
|
|
||||||
p.error('array comparison is not supported yet')
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
// p.fgen(' ${p.tok.str()} ')
|
|
||||||
if (is_float || is_str || is_ustr) && !p.is_js {
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
else if p.is_sql && tok == .eq {
|
|
||||||
p.gen('=')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen(tok.str())
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
// `id == user.id` => `id == $1`, `user.id`
|
|
||||||
if p.is_sql {
|
|
||||||
p.sql_i++
|
|
||||||
p.gen('$' + p.sql_i.str())
|
|
||||||
p.cgen.start_cut()
|
|
||||||
p.check_types(p.expression(), typ)
|
|
||||||
sql_param := p.cgen.cut()
|
|
||||||
p.sql_params << sql_param
|
|
||||||
p.sql_types << typ
|
|
||||||
// println('*** sql type: $typ | param: $sql_param')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.check_types(p.expression(), typ)
|
|
||||||
}
|
|
||||||
typ = 'bool'
|
|
||||||
if is_str && !p.is_js {
|
|
||||||
// && !p.is_sql {
|
|
||||||
p.gen(')')
|
|
||||||
match tok {
|
|
||||||
.eq {
|
|
||||||
p.cgen.set_placeholder(ph, 'string_eq(')
|
|
||||||
}
|
|
||||||
.ne {
|
|
||||||
p.cgen.set_placeholder(ph, 'string_ne(')
|
|
||||||
}
|
|
||||||
.le {
|
|
||||||
p.cgen.set_placeholder(ph, 'string_le(')
|
|
||||||
}
|
|
||||||
.ge {
|
|
||||||
p.cgen.set_placeholder(ph, 'string_ge(')
|
|
||||||
}
|
|
||||||
.gt {
|
|
||||||
p.cgen.set_placeholder(ph, 'string_gt(')
|
|
||||||
}
|
|
||||||
.lt {
|
|
||||||
p.cgen.set_placeholder(ph, 'string_lt(')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
if is_ustr {
|
|
||||||
p.gen(')')
|
|
||||||
match tok {
|
|
||||||
.eq {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_eq(')
|
|
||||||
}
|
|
||||||
.ne {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_ne(')
|
|
||||||
}
|
|
||||||
.le {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_le(')
|
|
||||||
}
|
|
||||||
.ge {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_ge(')
|
|
||||||
}
|
|
||||||
.gt {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_gt(')
|
|
||||||
}
|
|
||||||
.lt {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_lt(')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
if is_float && p.cur_fn.name != 'f32_abs' && p.cur_fn.name != 'f64_abs' {
|
|
||||||
p.gen(')')
|
|
||||||
match tok {
|
|
||||||
// NB: For more precision/stability, the == and != float
|
|
||||||
// comparisons are done with V functions that use the epsilon
|
|
||||||
// constants for the given type.
|
|
||||||
// Everything else uses native comparisons (C macros) for speed.
|
|
||||||
.eq {
|
|
||||||
p.cgen.set_placeholder(ph, '${expr_type}_eq(')
|
|
||||||
}
|
|
||||||
.ne {
|
|
||||||
p.cgen.set_placeholder(ph, '${expr_type}_ne(')
|
|
||||||
}
|
|
||||||
.le {
|
|
||||||
p.cgen.set_placeholder(ph, 'macro_${expr_type}_le(')
|
|
||||||
}
|
|
||||||
.ge {
|
|
||||||
p.cgen.set_placeholder(ph, 'macro_${expr_type}_ge(')
|
|
||||||
}
|
|
||||||
.gt {
|
|
||||||
p.cgen.set_placeholder(ph, 'macro_${expr_type}_gt(')
|
|
||||||
}
|
|
||||||
.lt {
|
|
||||||
p.cgen.set_placeholder(ph, 'macro_${expr_type}_lt(')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
// also called on *, &, @, . (enum)
|
|
||||||
fn (p mut Parser) name_expr() string {
|
|
||||||
p.has_immutable_field = false
|
|
||||||
p.is_const_literal = false
|
|
||||||
ph := p.cgen.add_placeholder()
|
|
||||||
// amp
|
|
||||||
ptr := p.tok == .amp
|
|
||||||
deref := p.tok == .mul
|
|
||||||
mut mul_nr := 0
|
|
||||||
mut deref_nr := 0
|
|
||||||
for {
|
|
||||||
if p.tok == .amp {
|
|
||||||
mul_nr++
|
|
||||||
}
|
|
||||||
else if p.tok == .mul {
|
|
||||||
deref_nr++
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
if p.tok == .lpar {
|
|
||||||
p.gen('*'.repeat(deref_nr))
|
|
||||||
p.gen('(')
|
|
||||||
p.check(.lpar)
|
|
||||||
mut temp_type := p.bool_expression()
|
|
||||||
p.gen(')')
|
|
||||||
p.check(.rpar)
|
|
||||||
for _ in 0 .. deref_nr {
|
|
||||||
temp_type = temp_type.replace_once('*', '')
|
|
||||||
}
|
|
||||||
return temp_type
|
|
||||||
}
|
|
||||||
mut name := p.lit
|
|
||||||
// blank identifier (not var)
|
|
||||||
if name == '_' {
|
|
||||||
p.error('cannot use `_` as value')
|
|
||||||
}
|
|
||||||
// generic type check
|
|
||||||
if name in p.generic_dispatch.inst.keys() {
|
|
||||||
name = p.generic_dispatch.inst[name]
|
|
||||||
}
|
|
||||||
// Raw string (`s := r'hello \n ')
|
|
||||||
if name == 'r' && p.peek() == .string&& p.prev_tok != .str_dollar {
|
|
||||||
p.string_expr()
|
|
||||||
return 'string'
|
|
||||||
}
|
|
||||||
// C string (a zero terminated one) C.func( c'hello' )
|
|
||||||
if name == 'c' && p.peek() == .string&& p.prev_tok != .str_dollar {
|
|
||||||
p.string_expr()
|
|
||||||
return 'charptr'
|
|
||||||
}
|
|
||||||
// known_type := p.table.known_type(name)
|
|
||||||
orig_name := name
|
|
||||||
is_c := name == 'C' && p.peek() == .dot
|
|
||||||
if is_c {
|
|
||||||
p.check(.name)
|
|
||||||
p.check(.dot)
|
|
||||||
name = p.lit
|
|
||||||
// C struct initialization
|
|
||||||
if p.peek() == .lcbr && p.expected_type == '' {
|
|
||||||
// not an expression
|
|
||||||
if !p.table.known_type(name) {
|
|
||||||
p.error('unknown C type `$name`, ' + 'define it with `struct C.$name { ... }`')
|
|
||||||
}
|
|
||||||
return p.get_struct_type(name, true, ptr)
|
|
||||||
}
|
|
||||||
if ptr && p.peek() == .lpar {
|
|
||||||
peek2 := p.tokens[p.token_idx + 1]
|
|
||||||
// `&C.Foo(0)` cast (replacing old `&C.Foo{!}`)
|
|
||||||
if peek2.tok == .number && peek2.lit == '0' {
|
|
||||||
p.cgen.insert_before('struct /*C.Foo(0)*/ ')
|
|
||||||
p.gen('0')
|
|
||||||
p.next()
|
|
||||||
p.next()
|
|
||||||
p.next()
|
|
||||||
p.next()
|
|
||||||
return name + '*'
|
|
||||||
}
|
|
||||||
// `&C.Foo(foo)` cast
|
|
||||||
p.cast(name + '*')
|
|
||||||
return name + '*'
|
|
||||||
}
|
|
||||||
// C function
|
|
||||||
if p.peek() == .lpar {
|
|
||||||
return p.get_c_func_type(name)
|
|
||||||
}
|
|
||||||
// C const (`C.GLFW_KEY_LEFT`)
|
|
||||||
p.gen(name)
|
|
||||||
p.next()
|
|
||||||
return 'int'
|
|
||||||
}
|
|
||||||
// enum value? (`color == .green`)
|
|
||||||
if p.tok == .dot {
|
|
||||||
if p.table.known_type(p.expected_type) {
|
|
||||||
p.check_enum_member_access()
|
|
||||||
// println("found enum value: $p.expected_type")
|
|
||||||
return p.expected_type
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.error('unknown enum: `$p.expected_type`')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Variable, checked before modules, so that module shadowing is allowed:
|
|
||||||
// `gg = gg.newcontext(); gg.draw_rect(...)`
|
|
||||||
if p.known_var_check_new_var(name) {
|
|
||||||
return p.get_var_type(name, ptr, deref_nr)
|
|
||||||
}
|
|
||||||
// Module?
|
|
||||||
if p.peek() == .dot && (name == p.mod || p.import_table.known_alias(name)) && !is_c {
|
|
||||||
mut mod := name
|
|
||||||
// must be aliased module
|
|
||||||
if name != p.mod && p.import_table.known_alias(name) {
|
|
||||||
p.import_table.register_used_import(name)
|
|
||||||
mod = p.import_table.resolve_alias(name)
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.check(.dot)
|
|
||||||
name = p.lit
|
|
||||||
name = prepend_mod(mod_gen_name(mod), name)
|
|
||||||
}
|
|
||||||
// Unknown name, try prepending the module name to it
|
|
||||||
// TODO perf
|
|
||||||
else if !p.table.known_type(name) && !p.known_fn_in_mod(name) && !p.table.known_const(name) && !is_c {
|
|
||||||
name = p.prepend_mod(name)
|
|
||||||
}
|
|
||||||
// re-check
|
|
||||||
if p.known_var_check_new_var(name) {
|
|
||||||
return p.get_var_type(name, ptr, deref_nr)
|
|
||||||
}
|
|
||||||
// if known_type || is_c_struct_init || (p.first_pass() && p.peek() == .lcbr) {
|
|
||||||
// known type? int(4.5) or Color.green (enum)
|
|
||||||
if p.table.known_type(name) {
|
|
||||||
// cast expression: float(5), byte(0), (*int)(ptr) etc
|
|
||||||
// if !is_c && ( p.peek() == .lpar || (deref && p.peek() == .rpar) ) {
|
|
||||||
if p.peek() == .lpar || (deref && p.peek() == .rpar) {
|
|
||||||
if deref {
|
|
||||||
name += '*'.repeat(deref_nr)
|
|
||||||
}
|
|
||||||
else if ptr {
|
|
||||||
name += '*'.repeat(mul_nr)
|
|
||||||
}
|
|
||||||
// p.gen('(')
|
|
||||||
mut typ := name
|
|
||||||
p.cast(typ)
|
|
||||||
// p.gen(')')
|
|
||||||
for p.tok == .dot {
|
|
||||||
typ = p.dot(typ, ph)
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
// Color.green
|
|
||||||
else if p.peek() == .dot {
|
|
||||||
is_arr_start := p.prev_tok == .lsbr
|
|
||||||
enum_type := p.table.find_type(name)
|
|
||||||
if enum_type.cat != .enum_ {
|
|
||||||
p.error('`$name` is not an enum')
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.check(.dot)
|
|
||||||
val := p.lit
|
|
||||||
if !enum_type.has_enum_val(val) {
|
|
||||||
p.error('enum `$enum_type.name` does not have value `$val`')
|
|
||||||
}
|
|
||||||
if p.expected_type == enum_type.name && !is_arr_start {
|
|
||||||
// `if color == .red` is enough
|
|
||||||
// no need in `if color == Color.red`
|
|
||||||
p.warn('`${enum_type.name}.$val` is unnecessary, use `.$val`')
|
|
||||||
}
|
|
||||||
// `expr := Expr.BoolExpr(true)` =>
|
|
||||||
// `Expr expr = { .obj = true, .typ = BoolExpr_type };`
|
|
||||||
if val[0].is_capital() {
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
//println('sum type $val name=$val')
|
|
||||||
// Find a corresponding tuple variant
|
|
||||||
// TODO slow, but this will be re-written anyway
|
|
||||||
mut idx := 0
|
|
||||||
for i, val_ in enum_type.enum_vals {
|
|
||||||
//println('f $field.name')
|
|
||||||
if val_ == val {
|
|
||||||
idx = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q := p.table.tuple_variants[enum_type.name]
|
|
||||||
//println(q)
|
|
||||||
//println(q[idx])
|
|
||||||
arg_type := q[idx]
|
|
||||||
p.gen('($enum_type.name) { .obj = ($arg_type[]) { ')
|
|
||||||
p.bool_expression()
|
|
||||||
p.check(.rpar)
|
|
||||||
p.gen('}, .typ = ${val}_type }')
|
|
||||||
return enum_type.name
|
|
||||||
}
|
|
||||||
// println('enum val $val')
|
|
||||||
p.gen(mod_gen_name(enum_type.mod) + '__' + enum_type.name + '_' + val) // `color = main__Color_green`
|
|
||||||
p.next()
|
|
||||||
return enum_type.name
|
|
||||||
}
|
|
||||||
// normal struct init (non-C)
|
|
||||||
else if p.peek() == .lcbr || p.peek() == .lt {
|
|
||||||
return p.get_struct_type(name, false, ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Constant
|
|
||||||
if p.table.known_const(name) {
|
|
||||||
return p.get_const_type(name, ptr)
|
|
||||||
}
|
|
||||||
// TODO: V script? Try os module.
|
|
||||||
// Function (not method, methods are handled in `.dot()`)
|
|
||||||
mut f := p.table.find_fn_is_script(name, p.v_script) or {
|
|
||||||
// First pass, the function can be defined later.
|
|
||||||
if p.first_pass() {
|
|
||||||
p.next()
|
|
||||||
return 'unresolved'
|
|
||||||
}
|
|
||||||
// exhaused all options type,enum,const,mod,var,fn etc
|
|
||||||
// so show undefined error (also checks typos)
|
|
||||||
p.undefined_error(name, orig_name)
|
|
||||||
return '' // panics
|
|
||||||
}
|
|
||||||
// no () after func, so func is an argument, just gen its name
|
|
||||||
// TODO verify this and handle errors
|
|
||||||
peek := p.peek()
|
|
||||||
if peek != .lpar && peek != .lt {
|
|
||||||
// Register anon fn type
|
|
||||||
fn_typ := Type{
|
|
||||||
name: f.typ_str() // 'fn (int, int) string'
|
|
||||||
|
|
||||||
mod: p.mod
|
|
||||||
func: f
|
|
||||||
}
|
|
||||||
p.table.register_type(fn_typ)
|
|
||||||
p.gen(p.table.fn_gen_name(f))
|
|
||||||
p.next()
|
|
||||||
return f.typ_str() // 'void*'
|
|
||||||
}
|
|
||||||
// TODO bring back
|
|
||||||
if f.typ == 'void' && !p.inside_if_expr {
|
|
||||||
// p.error('`$f.name` used as value')
|
|
||||||
}
|
|
||||||
fn_call_ph := p.cgen.add_placeholder()
|
|
||||||
// println('call to fn $f.name of type $f.typ')
|
|
||||||
// TODO replace the following dirty hacks (needs ptr access to fn table)
|
|
||||||
new_f := f
|
|
||||||
p.fn_call(mut new_f, 0, '', '')
|
|
||||||
if f.is_generic {
|
|
||||||
_ = p.table.find_fn(f.name) or {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
// println('after call of generic instance $new_f.name(${new_f.str_args(p.table)}) $new_f.typ')
|
|
||||||
// println(' from $f2.name(${f2.str_args(p.table)}) $f2.typ : $f2.type_inst')
|
|
||||||
}
|
|
||||||
f = new_f
|
|
||||||
// optional function call `function() or {}`, no return assignment
|
|
||||||
is_or_else := p.tok == .key_orelse
|
|
||||||
if p.tok == .question {
|
|
||||||
// `files := os.ls('.')?`
|
|
||||||
return p.gen_handle_question_suffix(f, fn_call_ph)
|
|
||||||
}
|
|
||||||
else if !p.is_var_decl && is_or_else {
|
|
||||||
f.typ = p.gen_handle_option_or_else(f.typ, '', fn_call_ph)
|
|
||||||
}
|
|
||||||
else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && f.typ.starts_with('Option_') {
|
|
||||||
opt_type := f.typ[7..].replace('ptr_', '&')
|
|
||||||
p.error('unhandled option type: `?$opt_type`')
|
|
||||||
}
|
|
||||||
// dot after a function call: `get_user().age`
|
|
||||||
if p.tok == .dot {
|
|
||||||
mut typ := ''
|
|
||||||
for p.tok == .dot {
|
|
||||||
// println('dot #$dc')
|
|
||||||
typ = p.dot(f.typ, ph)
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
// p.log('end of name_expr')
|
|
||||||
if f.typ.ends_with('*') {
|
|
||||||
p.is_alloc = true
|
|
||||||
}
|
|
||||||
return f.typ
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns resulting type
|
|
||||||
fn (p mut Parser) expression() string {
|
|
||||||
p.is_const_literal = true
|
|
||||||
// if p.scanner.file_path.contains('test_test') {
|
|
||||||
// println('expression() pass=$p.pass tok=')
|
|
||||||
// p.print_tok()
|
|
||||||
// }
|
|
||||||
ph := p.cgen.add_placeholder()
|
|
||||||
typ := p.indot_expr()
|
|
||||||
is_str := typ == 'string'
|
|
||||||
is_ustr := typ == 'ustring'
|
|
||||||
// `a << b` ==> `array_push(&a, b)`
|
|
||||||
if p.tok == .left_shift {
|
|
||||||
if typ.contains('array_') {
|
|
||||||
// Can't pass integer literal, because push requires a void*
|
|
||||||
// a << 7 => int tmp = 7; array_push(&a, &tmp);
|
|
||||||
// _PUSH(&a, expression(), tmp, string)
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
tmp_typ := parse_pointer(typ[6..]) // skip "array_"
|
|
||||||
//p.warn('arr typ $tmp_typ')
|
|
||||||
p.expected_type = tmp_typ
|
|
||||||
//println('set expr to $tmp_typ')
|
|
||||||
p.check_space(.left_shift)
|
|
||||||
// Get the value we are pushing
|
|
||||||
p.gen(', (')
|
|
||||||
// Immutable? Can we push?
|
|
||||||
if !p.expr_var.is_mut && !p.pref.translated {
|
|
||||||
p.error("`$p.expr_var.name` is immutable (can\'t <<)")
|
|
||||||
}
|
|
||||||
if p.expr_var.is_arg && p.expr_var.typ.starts_with('array_') {
|
|
||||||
p.error("for now it's not possible to append an element to " + 'a mutable array argument `$p.expr_var.name`')
|
|
||||||
}
|
|
||||||
if !p.expr_var.is_changed {
|
|
||||||
p.mark_var_changed(p.expr_var)
|
|
||||||
}
|
|
||||||
p.gen('/*typ = $typ tmp_typ=$tmp_typ*/')
|
|
||||||
ph_clone := p.cgen.add_placeholder()
|
|
||||||
expr_type := p.bool_expression()
|
|
||||||
// Need to clone the string when appending it to an array?
|
|
||||||
if p.pref.autofree && typ == 'array_string' && expr_type == 'string' {
|
|
||||||
p.cgen.set_placeholder(ph_clone, 'string_clone(')
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
p.gen_array_push(ph, typ, expr_type, tmp, tmp_typ)
|
|
||||||
return 'void'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if !is_integer_type(typ) {
|
|
||||||
t := p.table.find_type(typ)
|
|
||||||
if t.cat != .enum_ {
|
|
||||||
p.error('cannot use shift operator on non-integer type `$typ`')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.gen(' << ')
|
|
||||||
p.check_types(p.expression(), 'integer')
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.tok == .righ_shift {
|
|
||||||
if !is_integer_type(typ) {
|
|
||||||
t := p.table.find_type(typ)
|
|
||||||
if t.cat != .enum_ {
|
|
||||||
p.error('cannot use shift operator on non-integer type `$typ`')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.gen(' >> ')
|
|
||||||
p.check_types(p.expression(), 'integer')
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
// + - | ^
|
|
||||||
for p.tok in [.plus, .minus, .pipe, .amp, .xor] {
|
|
||||||
tok_op := p.tok
|
|
||||||
if typ == 'bool' {
|
|
||||||
p.error('operator ${p.tok.str()} not defined on bool ')
|
|
||||||
}
|
|
||||||
is_num := typ.contains('*') || is_number_type(typ) || is_number_type(p.base_type(typ))
|
|
||||||
p.check_space(p.tok)
|
|
||||||
if is_str && tok_op == .plus && !p.is_js {
|
|
||||||
p.is_alloc = true
|
|
||||||
p.cgen.set_placeholder(ph, 'string_add(')
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
else if is_ustr && tok_op == .plus {
|
|
||||||
p.cgen.set_placeholder(ph, 'ustring_add(')
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
// 3 + 4
|
|
||||||
else if is_num || p.is_js {
|
|
||||||
if typ == 'void*' {
|
|
||||||
// Msvc errors on void* pointer arithmatic
|
|
||||||
// ... So cast to byte* and then do the add
|
|
||||||
p.cgen.set_placeholder(ph, '(byte*)')
|
|
||||||
}
|
|
||||||
else if typ.contains('*') {
|
|
||||||
p.cgen.set_placeholder(ph, '($typ)')
|
|
||||||
}
|
|
||||||
p.gen(tok_op.str())
|
|
||||||
}
|
|
||||||
// Vec + Vec
|
|
||||||
else {
|
|
||||||
if p.pref.translated {
|
|
||||||
p.gen(tok_op.str() + ' /*doom hack*/') // TODO hack to fix DOOM's angle_t
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is_str && tok_op != .plus {
|
|
||||||
p.error('strings only support `+` operator')
|
|
||||||
}
|
|
||||||
expr_type := p.term()
|
|
||||||
open := tok_op == .amp && p.tok in [.eq, .ne] // force precedence `(a & b) == c` //false
|
|
||||||
if tok_op in [.pipe, .amp, .xor] {
|
|
||||||
if !(is_integer_type(expr_type) && is_integer_type(typ)) {
|
|
||||||
p.error('operator ${tok_op.str()} is defined only on integer types')
|
|
||||||
}
|
|
||||||
// open = true
|
|
||||||
}
|
|
||||||
if open {
|
|
||||||
p.cgen.set_placeholder(ph, '(')
|
|
||||||
}
|
|
||||||
p.check_types(expr_type, typ)
|
|
||||||
if (is_str || is_ustr) && tok_op == .plus && !p.is_js {
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
if open {
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
// Make sure operators are used with correct types
|
|
||||||
if !p.pref.translated && !is_str && !is_ustr && !is_num {
|
|
||||||
T := p.table.find_type(typ)
|
|
||||||
if tok_op == .plus {
|
|
||||||
p.handle_operator('+', typ, 'op_plus', ph, T)
|
|
||||||
}
|
|
||||||
else if tok_op == .minus {
|
|
||||||
p.handle_operator('-', typ, 'op_minus', ph, T)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `as` cast
|
|
||||||
// TODO remove copypasta
|
|
||||||
if p.tok == .key_as {
|
|
||||||
return p.key_as(typ, ph)
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) handle_operator(op string, typ string,cpostfix string, ph int, tt &Type) {
|
|
||||||
if tt.has_method(op) {
|
|
||||||
p.cgen.set_placeholder(ph, '${typ}_${cpostfix}(')
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
else if typ != 'unresolved' {
|
|
||||||
p.error('operator $op not defined on `$typ`')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) term() string {
|
|
||||||
line_nr := p.scanner.line_nr
|
|
||||||
// if p.fileis('fn_test') {
|
|
||||||
// println('\nterm() $line_nr')
|
|
||||||
// }
|
|
||||||
ph := p.cgen.add_placeholder()
|
|
||||||
typ := p.unary()
|
|
||||||
// if p.fileis('fn_test') {
|
|
||||||
// println('2: $line_nr')
|
|
||||||
// }
|
|
||||||
// `*` on a newline? Can't be multiplication, only dereference
|
|
||||||
if p.tok == .mul && line_nr != p.scanner.line_nr {
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
for p.tok in [.mul, .div, .mod] {
|
|
||||||
tok := p.tok
|
|
||||||
is_mul := tok == .mul
|
|
||||||
is_div := tok == .div
|
|
||||||
is_mod := tok == .mod
|
|
||||||
p.fspace()
|
|
||||||
p.next()
|
|
||||||
p.gen(tok.str()) // + ' /*op2*/ ')
|
|
||||||
oph := p.cgen.add_placeholder()
|
|
||||||
p.fspace()
|
|
||||||
if (is_div || is_mod) && p.tok == .number && p.lit == '0' {
|
|
||||||
p.error('division or modulo by zero')
|
|
||||||
}
|
|
||||||
expr_type := p.unary()
|
|
||||||
if (is_mul || is_div) && expr_type == 'string' {
|
|
||||||
p.error('operator ${tok.str()} cannot be used on strings')
|
|
||||||
}
|
|
||||||
if !is_primitive_type(expr_type) && expr_type == typ {
|
|
||||||
p.check_types(expr_type, typ)
|
|
||||||
T := p.table.find_type(typ)
|
|
||||||
// NB: oph is a char index just after the OP
|
|
||||||
before_oph := p.cgen.cur_line[..oph - 1]
|
|
||||||
after_oph := p.cgen.cur_line[oph..]
|
|
||||||
p.cgen.cur_line = before_oph + ',' + after_oph
|
|
||||||
match tok {
|
|
||||||
.mul {
|
|
||||||
p.handle_operator('*', typ, 'op_mul', ph, T)
|
|
||||||
}
|
|
||||||
.div {
|
|
||||||
p.handle_operator('/', typ, 'op_div', ph, T)
|
|
||||||
}
|
|
||||||
.mod {
|
|
||||||
p.handle_operator('%', typ, 'op_mod', ph, T)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
}}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if is_mod {
|
|
||||||
if !(is_integer_type(expr_type) && is_integer_type(typ)) {
|
|
||||||
p.error('operator `mod` requires integer types')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.check_types(expr_type, typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) unary() string {
|
|
||||||
mut typ := ''
|
|
||||||
tok := p.tok
|
|
||||||
match tok {
|
|
||||||
.not {
|
|
||||||
p.gen('!')
|
|
||||||
p.check(.not)
|
|
||||||
// typ should be bool type
|
|
||||||
typ = p.indot_expr()
|
|
||||||
if typ != 'bool' {
|
|
||||||
p.error('operator ! requires bool type, not `$typ`')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bit_not {
|
|
||||||
p.gen('~')
|
|
||||||
p.check(.bit_not)
|
|
||||||
typ = p.bool_expression()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
typ = p.factor()
|
|
||||||
}}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) factor() string {
|
|
||||||
mut typ := ''
|
|
||||||
tok := p.tok
|
|
||||||
match tok {
|
|
||||||
.key_none {
|
|
||||||
if !p.expected_type.starts_with('Option_') {
|
|
||||||
p.error('need "$p.expected_type" got none')
|
|
||||||
}
|
|
||||||
p.gen('opt_none()')
|
|
||||||
p.check(.key_none)
|
|
||||||
return p.expected_type
|
|
||||||
}
|
|
||||||
.number {
|
|
||||||
// Check if float (`1.0`, `1e+3`) but not if is hexa (e.g. 0xEE contains `E` but is not float)
|
|
||||||
typ = if (p.lit.contains('.') || p.lit.contains('e') || p.lit.contains('E')) && !(p.lit[..2] in ['0x', '0X']) { 'f64' } else { 'int' }
|
|
||||||
if p.expected_type != '' && !is_valid_int_const(p.lit, p.expected_type) {
|
|
||||||
p.error('constant `$p.lit` overflows `$p.expected_type`')
|
|
||||||
}
|
|
||||||
p.gen(p.lit)
|
|
||||||
}
|
|
||||||
.minus {
|
|
||||||
p.gen('-')
|
|
||||||
p.next()
|
|
||||||
return p.factor()
|
|
||||||
// Variable
|
|
||||||
}
|
|
||||||
.key_sizeof {
|
|
||||||
p.gen('sizeof(')
|
|
||||||
// p.fgen('sizeof(')
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
mut sizeof_typ := p.get_type()
|
|
||||||
p.check(.rpar)
|
|
||||||
p.gen('$sizeof_typ)')
|
|
||||||
// p.fgen('$sizeof_typ)')
|
|
||||||
return 'int'
|
|
||||||
}
|
|
||||||
.key_typeof {
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
p.cgen.nogen = true
|
|
||||||
vname := if p.tok == .name && p.peek() == .rpar { p.lit } else { '' }
|
|
||||||
type_of_var := p.expression()
|
|
||||||
p.cgen.nogen = false
|
|
||||||
p.check(.rpar)
|
|
||||||
is_sum_type := type_of_var in p.table.sum_types
|
|
||||||
if is_sum_type && vname.len > 0 {
|
|
||||||
// TODO: make this work for arbitrary sumtype expressions, not just simple vars
|
|
||||||
// NB: __SumTypeNames__[xxx][0] is the name of the sumtype itself;
|
|
||||||
// idx>0 are the names of the sumtype children
|
|
||||||
p.gen('tos3(__SumTypeNames__${type_of_var}[${vname}.typ])')
|
|
||||||
}else{
|
|
||||||
p.gen('tos3("$type_of_var")')
|
|
||||||
}
|
|
||||||
return 'string'
|
|
||||||
}
|
|
||||||
.key_nameof {
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
mut nameof_typ := p.get_type()
|
|
||||||
p.check(.rpar)
|
|
||||||
p.gen('tos3("$nameof_typ")')
|
|
||||||
return 'string'
|
|
||||||
}
|
|
||||||
.key_offsetof {
|
|
||||||
p.next()
|
|
||||||
p.check(.lpar)
|
|
||||||
offsetof_typ := p.get_type()
|
|
||||||
p.check(.comma)
|
|
||||||
member := p.check_name()
|
|
||||||
p.check(.rpar)
|
|
||||||
p.gen('__offsetof($offsetof_typ, $member)')
|
|
||||||
return 'int'
|
|
||||||
}
|
|
||||||
.amp, .dot, .mul {
|
|
||||||
// (dot is for enum vals: `.green`)
|
|
||||||
return p.name_expr()
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
// map[string]int
|
|
||||||
if p.lit == 'map' && p.peek() == .lsbr {
|
|
||||||
return p.map_init()
|
|
||||||
}
|
|
||||||
if p.lit == 'json' && p.peek() == .dot {
|
|
||||||
if !('json' in p.table.imports) {
|
|
||||||
p.error('undefined: `json`, use `import json`')
|
|
||||||
}
|
|
||||||
p.import_table.register_used_import('json')
|
|
||||||
return p.js_decode()
|
|
||||||
}
|
|
||||||
// if p.fileis('orm_test') {
|
|
||||||
// println('ORM name: $p.lit')
|
|
||||||
// }
|
|
||||||
typ = p.name_expr()
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
.key_default {
|
|
||||||
p.next()
|
|
||||||
p.next()
|
|
||||||
name := p.check_name()
|
|
||||||
if name != 'T' {
|
|
||||||
p.error('default needs T')
|
|
||||||
}
|
|
||||||
p.gen('default(T)')
|
|
||||||
p.next()
|
|
||||||
return 'T'
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
.lpar {
|
|
||||||
// p.gen('(/*lpar*/')
|
|
||||||
p.gen('(')
|
|
||||||
p.check(.lpar)
|
|
||||||
typ = p.bool_expression()
|
|
||||||
// Hack. If this `)` referes to a ptr cast `(*int__)__`, it was already checked
|
|
||||||
// TODO: fix parser so that it doesn't think it's a par expression when it sees `(` in
|
|
||||||
// __(__*int)(
|
|
||||||
if !p.ptr_cast {
|
|
||||||
p.check(.rpar)
|
|
||||||
}
|
|
||||||
p.ptr_cast = false
|
|
||||||
p.gen(')')
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
.chartoken {
|
|
||||||
p.char_expr()
|
|
||||||
typ = 'byte'
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
.string{
|
|
||||||
p.string_expr()
|
|
||||||
typ = 'string'
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
.key_false {
|
|
||||||
typ = 'bool'
|
|
||||||
p.gen('0')
|
|
||||||
}
|
|
||||||
.key_true {
|
|
||||||
typ = 'bool'
|
|
||||||
p.gen('1')
|
|
||||||
}
|
|
||||||
.lsbr {
|
|
||||||
// `[1,2,3]` or `[]` or `[20]byte`
|
|
||||||
// TODO have to return because arrayInit does next()
|
|
||||||
// everything should do next()
|
|
||||||
return p.array_init()
|
|
||||||
}
|
|
||||||
.lcbr {
|
|
||||||
// `m := { 'one': 1 }`
|
|
||||||
if p.peek() == .string{
|
|
||||||
return p.map_init()
|
|
||||||
}
|
|
||||||
peek2 := p.tokens[p.token_idx + 1]
|
|
||||||
if p.peek() == .rcbr || (p.peek() == .name && peek2.tok == .colon) {
|
|
||||||
return p.struct_init(p.expected_type)
|
|
||||||
}
|
|
||||||
// { user | name :'new name' }
|
|
||||||
return p.assoc()
|
|
||||||
}
|
|
||||||
.key_if {
|
|
||||||
typ = p.if_statement(true, 0)
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
.key_match {
|
|
||||||
typ = p.match_statement(true)
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if p.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
next := p.peek()
|
|
||||||
println('prev=${p.prev_tok.str()}')
|
|
||||||
println('next=${next.str()}')
|
|
||||||
}
|
|
||||||
p.error('unexpected token: `${p.tok.str()}`')
|
|
||||||
}}
|
|
||||||
p.next() // TODO everything should next()
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
// { user | name: 'new name' }
|
|
||||||
|
|
1742
vlib/compiler/fn.v
1742
vlib/compiler/fn.v
File diff suppressed because it is too large
Load Diff
|
@ -1,227 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
fn (p mut Parser) for_st() {
|
|
||||||
p.check(.key_for)
|
|
||||||
p.for_expr_cnt++
|
|
||||||
next_tok := p.peek()
|
|
||||||
if p.tok != .lcbr {
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
// debug := p.scanner.file_path.contains('r_draw')
|
|
||||||
p.open_scope()
|
|
||||||
//mut label := 0
|
|
||||||
mut to := 0
|
|
||||||
if p.tok == .lcbr {
|
|
||||||
// Infinite loop
|
|
||||||
p.gen('while (1) {')
|
|
||||||
}
|
|
||||||
else if p.tok == .key_mut {
|
|
||||||
p.error('`mut` is not required in for loops')
|
|
||||||
}
|
|
||||||
// for i := 0; i < 10; i++ {
|
|
||||||
else if next_tok == .decl_assign || next_tok == .assign || p.tok == .semicolon {
|
|
||||||
p.genln('for (')
|
|
||||||
if next_tok == .decl_assign {
|
|
||||||
p.check_not_reserved()
|
|
||||||
p.var_decl()
|
|
||||||
}
|
|
||||||
else if p.tok != .semicolon {
|
|
||||||
// allow `for ;; i++ {`
|
|
||||||
// Allow `for i = 0; i < ...`
|
|
||||||
p.statement(false)
|
|
||||||
}
|
|
||||||
p.check(.semicolon)
|
|
||||||
p.gen(' ; ')
|
|
||||||
p.fspace()
|
|
||||||
if p.tok != .semicolon {
|
|
||||||
p.bool_expression()
|
|
||||||
}
|
|
||||||
p.check(.semicolon)
|
|
||||||
p.gen(' ; ')
|
|
||||||
p.fspace()
|
|
||||||
if p.tok != .lcbr {
|
|
||||||
p.statement(false)
|
|
||||||
}
|
|
||||||
p.genln(') { ')
|
|
||||||
}
|
|
||||||
// for i, val in array
|
|
||||||
else if p.peek() == .comma {
|
|
||||||
/*
|
|
||||||
`for i, val in array {`
|
|
||||||
==>
|
|
||||||
```
|
|
||||||
array_int tmp = array;
|
|
||||||
for (int i = 0; i < tmp.len; i++) {
|
|
||||||
int val = tmp[i];
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
i := p.check_name()
|
|
||||||
p.check(.comma)
|
|
||||||
p.fspace()
|
|
||||||
val := p.check_name()
|
|
||||||
if i == '_' && val == '_' {
|
|
||||||
p.error('no new variables on the left side of `in`')
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
p.check(.key_in)
|
|
||||||
p.fspace()
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
mut typ,expr := p.tmp_expr()
|
|
||||||
is_arr := typ.starts_with('array_')
|
|
||||||
is_map := typ.starts_with('map_')
|
|
||||||
is_str := typ == 'string'
|
|
||||||
is_variadic_arg := typ.starts_with('varg_')
|
|
||||||
if !is_arr && !is_str && !is_map && !is_variadic_arg {
|
|
||||||
p.error('cannot range over type `$typ`')
|
|
||||||
}
|
|
||||||
if !is_variadic_arg {
|
|
||||||
if p.is_js {
|
|
||||||
p.genln('var $tmp = $expr;')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.genln('$typ $tmp = $expr;')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// typ = strings.Replace(typ, "_ptr", "*", -1)
|
|
||||||
mut i_var_type := 'int'
|
|
||||||
if is_variadic_arg {
|
|
||||||
typ = typ[5..]
|
|
||||||
p.gen_for_varg_header(i, expr, typ, val)
|
|
||||||
}
|
|
||||||
else if is_arr {
|
|
||||||
typ = parse_pointer(typ[6..])
|
|
||||||
p.gen_for_header(i, tmp, typ, val)
|
|
||||||
}
|
|
||||||
else if is_map {
|
|
||||||
i_var_type = 'string'
|
|
||||||
typ = parse_pointer(typ[4..])
|
|
||||||
p.gen_for_map_header(i, tmp, typ, val, typ)
|
|
||||||
}
|
|
||||||
else if is_str {
|
|
||||||
typ = 'byte'
|
|
||||||
p.gen_for_str_header(i, tmp, typ, val)
|
|
||||||
}
|
|
||||||
// Register temp vars
|
|
||||||
if i != '_' {
|
|
||||||
if p.known_var(i) {
|
|
||||||
p.error('redefinition of `$i`')
|
|
||||||
}
|
|
||||||
p.register_var(Var{
|
|
||||||
name: i
|
|
||||||
typ: i_var_type
|
|
||||||
is_mut: true
|
|
||||||
is_changed: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if val != '_' {
|
|
||||||
if p.known_var(val) {
|
|
||||||
p.error('redefinition of `$val`')
|
|
||||||
}
|
|
||||||
p.register_var(Var{
|
|
||||||
name: val
|
|
||||||
typ: typ
|
|
||||||
ptr: typ.contains('*')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `for val in vals`
|
|
||||||
else if p.peek() == .key_in || p.peek() == .left_arrow {
|
|
||||||
p.check_not_reserved()
|
|
||||||
val := p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
//p.check(.key_in)
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
mut typ,expr := p.tmp_expr()
|
|
||||||
is_range := p.tok == .dotdot
|
|
||||||
is_variadic_arg := typ.starts_with('varg_')
|
|
||||||
mut range_end := ''
|
|
||||||
if is_range {
|
|
||||||
p.check_types(typ, 'int')
|
|
||||||
p.check_space(.dotdot)
|
|
||||||
if p.pref.backend == .x64 {
|
|
||||||
to = p.lit.int()
|
|
||||||
}
|
|
||||||
range_typ,range_expr := p.tmp_expr()
|
|
||||||
p.check_types(range_typ, 'int')
|
|
||||||
range_end = range_expr
|
|
||||||
if p.pref.backend == .x64 {
|
|
||||||
//label = p.x64.gen_loop_start(expr.int())
|
|
||||||
// to = range_expr.int() // TODO why empty?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is_arr := typ.contains('array')
|
|
||||||
is_fixed := typ.starts_with('[')
|
|
||||||
is_str := typ == 'string'
|
|
||||||
if !is_arr && !is_str && !is_range && !is_fixed && !is_variadic_arg {
|
|
||||||
p.error('cannot range over type `$typ`')
|
|
||||||
}
|
|
||||||
if !is_variadic_arg {
|
|
||||||
if p.is_js {
|
|
||||||
p.genln('var $tmp = $expr;')
|
|
||||||
}
|
|
||||||
else if !is_fixed {
|
|
||||||
// Don't copy if it's a fixed array
|
|
||||||
p.genln('$typ $tmp = $expr;')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO var_type := if...
|
|
||||||
i := p.get_tmp()
|
|
||||||
if is_variadic_arg {
|
|
||||||
typ = typ[5..]
|
|
||||||
p.gen_for_varg_header(i, expr, typ, val)
|
|
||||||
}
|
|
||||||
else if is_range {
|
|
||||||
typ = 'int'
|
|
||||||
p.gen_for_range_header(i, range_end, tmp, typ, val)
|
|
||||||
}
|
|
||||||
else if is_arr {
|
|
||||||
typ = parse_pointer(typ[6..]) // all after `array_`
|
|
||||||
p.gen_for_header(i, tmp, typ, val)
|
|
||||||
}
|
|
||||||
else if is_str {
|
|
||||||
typ = 'byte'
|
|
||||||
p.gen_for_str_header(i, tmp, typ, val)
|
|
||||||
}
|
|
||||||
else if is_fixed {
|
|
||||||
typ = typ.all_after(']')
|
|
||||||
p.gen_for_fixed_header(i, expr, typ, val)
|
|
||||||
}
|
|
||||||
// println('for typ=$typ vartyp=$var_typ')
|
|
||||||
// Register temp var
|
|
||||||
if val != '_' {
|
|
||||||
if p.known_var(val) {
|
|
||||||
p.error('redefinition of `$val`')
|
|
||||||
}
|
|
||||||
p.register_var(Var{
|
|
||||||
name: val
|
|
||||||
typ: typ
|
|
||||||
ptr: typ.contains('*')
|
|
||||||
is_changed: true
|
|
||||||
is_mut: false
|
|
||||||
is_for_var: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// `for a < b {`
|
|
||||||
p.gen('while (')
|
|
||||||
p.check_types(p.bool_expression(), 'bool')
|
|
||||||
p.genln(') {')
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.genln('') // TODO why is this needed?
|
|
||||||
p.statements()
|
|
||||||
p.close_scope()
|
|
||||||
p.for_expr_cnt--
|
|
||||||
p.returns = false // TODO handle loops that are guaranteed to return
|
|
||||||
//if label > 0 {
|
|
||||||
//p.x64.gen_loop_end(to, label)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,757 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
const (
|
|
||||||
dot_ptr = '->'
|
|
||||||
)
|
|
||||||
// returns the type of the new variable
|
|
||||||
fn (p mut Parser) gen_var_decl(name string, is_static bool) string {
|
|
||||||
p.is_var_decl = true
|
|
||||||
mut typ := p.bool_expression()
|
|
||||||
// mut typ, expr := p.tmp_expr()
|
|
||||||
p.is_var_decl = false
|
|
||||||
if typ.starts_with('...') {
|
|
||||||
typ = typ[3..]
|
|
||||||
}
|
|
||||||
// p.gen('/*after expr*/')
|
|
||||||
// Option check ? or {
|
|
||||||
or_else := p.tok == .key_orelse
|
|
||||||
if or_else {
|
|
||||||
return p.gen_handle_option_or_else(typ, name, 0)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
// Now that we know the type, prepend it
|
|
||||||
// `[typ] [name] = bool_expression();`
|
|
||||||
// p.cgen.prepend_to_statement(nt_gen)
|
|
||||||
p.cgen.set_placeholder(0, nt_gen)
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_fn_decl(f Fn, typ, str_args string) {
|
|
||||||
dll_export_linkage := if p.pref.ccompiler == '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)
|
|
||||||
|
|
||||||
if p.attr == 'live' && p.pref.is_so {
|
|
||||||
// See fn.v for details about impl_live_ functions
|
|
||||||
p.genln('$typ impl_live_${fn_name_cgen} ($str_args);')
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
//is_indexer := p.peek() == .lsbr
|
|
||||||
is_fn_call,next_expr := p.is_expr_fn_call(p.token_idx)
|
|
||||||
pos := p.cgen.add_placeholder()
|
|
||||||
expr_tok := p.cur_tok_index()
|
|
||||||
p.is_var_decl = true
|
|
||||||
typ := p.bool_expression()
|
|
||||||
if typ == 'void' {
|
|
||||||
p.error_with_token_index('${next_expr}() $err_used_as_value', expr_tok)
|
|
||||||
}
|
|
||||||
p.is_var_decl = false
|
|
||||||
//if !is_indexer && !is_fn_call {
|
|
||||||
//p.error_with_token_index('assigning `$next_expr` to `_` is redundant', assign_error_tok_idx)
|
|
||||||
//}
|
|
||||||
// handle or
|
|
||||||
if p.tok == .key_orelse {
|
|
||||||
p.gen_handle_option_or_else(typ, '', pos)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if is_fn_call {
|
|
||||||
p.gen(';')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.cgen.resetln('{$typ _ = $p.cgen.cur_line;}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_handle_option_or_else(_typ, name string, fn_call_ph int) string {
|
|
||||||
mut typ := _typ
|
|
||||||
if !typ.starts_with('Option_') {
|
|
||||||
p.error('`or` block cannot be applied to non-optional type')
|
|
||||||
}
|
|
||||||
is_assign := name.len > 0
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
p.cgen.set_placeholder(fn_call_ph, '$typ $tmp = ')
|
|
||||||
typ = parse_pointer(typ[7..])
|
|
||||||
p.genln(';')
|
|
||||||
or_tok_idx := p.token_idx
|
|
||||||
p.fspace()
|
|
||||||
p.check(.key_orelse)
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.fspace()
|
|
||||||
p.register_var(Var{
|
|
||||||
name: 'err'
|
|
||||||
typ: 'string'
|
|
||||||
is_mut: false
|
|
||||||
is_used: true
|
|
||||||
})
|
|
||||||
p.register_var(Var{
|
|
||||||
name: 'errcode'
|
|
||||||
typ: 'int'
|
|
||||||
is_mut: false
|
|
||||||
is_used: true
|
|
||||||
})
|
|
||||||
if is_assign && !name.contains('.') && !p.is_var_decl {
|
|
||||||
// don't initialize struct fields
|
|
||||||
p.genln('$typ $name;')
|
|
||||||
}
|
|
||||||
p.genln('if (!$tmp .ok) {')
|
|
||||||
p.genln('string err = $tmp . error;')
|
|
||||||
p.genln('int errcode = $tmp . ecode;')
|
|
||||||
last_ph := p.cgen.add_placeholder()
|
|
||||||
last_typ := p.statements()
|
|
||||||
if is_assign && last_typ == typ {
|
|
||||||
// workaround for -g with default optional value
|
|
||||||
// when p.cgen.line_directives is true an extra
|
|
||||||
// line is added so we need to account for that
|
|
||||||
expr_line := if p.cgen.line_directives { p.cgen.lines[p.cgen.lines.len - 3] } else { p.cgen.lines[p.cgen.lines.len - 2] }
|
|
||||||
last_expr := expr_line[last_ph..]
|
|
||||||
p.cgen.lines[p.cgen.lines.len - 2] = ''
|
|
||||||
// same here
|
|
||||||
if p.cgen.line_directives {
|
|
||||||
p.cgen.lines[p.cgen.lines.len - 3] = ''
|
|
||||||
}
|
|
||||||
p.genln('if ($tmp .ok) {')
|
|
||||||
p.genln('$name = *($typ*) $tmp . data;')
|
|
||||||
p.genln('} else {')
|
|
||||||
p.genln('$name = $last_expr')
|
|
||||||
p.genln('}')
|
|
||||||
}
|
|
||||||
else if is_assign {
|
|
||||||
p.genln('$name = *($typ*)${tmp}.data;')
|
|
||||||
}
|
|
||||||
if !p.returns && last_typ != typ && is_assign && !(p.prev_tok2 in [.key_continue, .key_break]) {
|
|
||||||
p.error_with_token_index('`or` block must provide a default value or return/exit/continue/break/panic', or_tok_idx)
|
|
||||||
}
|
|
||||||
p.returns = false
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
// `files := os.ls('.')?`
|
|
||||||
fn (p mut Parser) gen_handle_question_suffix(f Fn, ph int) string {
|
|
||||||
if p.cur_fn.name != 'main__main' {
|
|
||||||
p.error('`func()?` syntax can only be used inside `fn main()` for now')
|
|
||||||
}
|
|
||||||
p.check(.question)
|
|
||||||
tmp := p.get_tmp()
|
|
||||||
p.cgen.set_placeholder(ph, '$f.typ $tmp = ')
|
|
||||||
p.genln(';')
|
|
||||||
p.genln('if (!${tmp}.ok) v_panic(${tmp}.error);')
|
|
||||||
typ := f.typ[7..] // option_xxx
|
|
||||||
p.gen('*($typ*) ${tmp}.data;')
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if !(t.cat in [.union_, .struct_, .objc_interface, .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 {')
|
|
||||||
if t.cat == .interface_ {
|
|
||||||
sb.writeln('\tvoid* _object;')
|
|
||||||
sb.writeln('\tint _interface_idx; // int t')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 IndexConfig) {
|
|
||||||
// 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[fn_ph..]
|
|
||||||
p.cgen.resetln(p.cgen.tmp_line[..fn_ph])
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
index_expr = p.cgen.cur_line[fn_ph..]
|
|
||||||
p.cgen.resetln(p.cgen.cur_line[..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 {
|
|
||||||
ref := if cfg.is_ptr { '*' } else { '' }
|
|
||||||
if cfg.is_slice {
|
|
||||||
p.gen(' array_slice2($ref $index_expr) ')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('( *($typ*) array_get($ref $index_expr) )')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if cfg.is_str && !p.builtin_mod {
|
|
||||||
if p.pref.is_bare {
|
|
||||||
p.gen(index_expr)
|
|
||||||
}
|
|
||||||
else if cfg.is_slice {
|
|
||||||
p.gen('string_substr2($index_expr)')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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(' ', '')
|
|
||||||
if f.name.len == 1 {
|
|
||||||
match f.name[0] {
|
|
||||||
`+` {
|
|
||||||
name = name.replace('+', 'op_plus')
|
|
||||||
}
|
|
||||||
`-` {
|
|
||||||
name = name.replace('-', 'op_minus')
|
|
||||||
}
|
|
||||||
`*` {
|
|
||||||
name = name.replace('*', 'op_mul')
|
|
||||||
}
|
|
||||||
`/` {
|
|
||||||
name = name.replace('/', 'op_div')
|
|
||||||
}
|
|
||||||
`%` {
|
|
||||||
name = name.replace('%', 'op_mod')
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f.is_interface {
|
|
||||||
// iname := f.args[0].typ // Speaker
|
|
||||||
// var := p.expr_var.name
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
// Avoid name conflicts (with things like abs(), print() etc).
|
|
||||||
// Generate v_abs(), v_print()
|
|
||||||
// TODO duplicate functionality
|
|
||||||
if f.mod == 'builtin' && f.name in c_reserved {
|
|
||||||
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 in ['main', 'WinMain', 'main__main', 'gg__vec2', 'build_token_str', 'build_keys']) && !(f.mod in ['builtin', 'darwin', 'os', '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 &Var, receiver_type string, cgen_name string, ftyp string, 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 = parse_pointer(receiver_type.all_after('array_'))
|
|
||||||
cast = '*($cast*) '
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cast = '(voidptr) '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.cgen.set_placeholder(method_ph, '$cast $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_fixed_header(i, tmp, var_typ, val string) {
|
|
||||||
p.genln('for (int $i = 0; $i < sizeof(${tmp}) / sizeof($tmp [0]); $i++) {')
|
|
||||||
if val == '_' {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.genln('$var_typ $val = $tmp[$i];')
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) {
|
|
||||||
// TODO var_typ is always byte
|
|
||||||
// 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];')
|
|
||||||
p.genln('$var_typ $val = ${tmp}.str[$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[assign_pos..]
|
|
||||||
p.cgen.resetln(p.cgen.cur_line[..assign_pos])
|
|
||||||
mut cao_tmp := p.cgen.cur_line
|
|
||||||
mut func := ''
|
|
||||||
if is_map {
|
|
||||||
if is_ptr {
|
|
||||||
func = 'map_set('
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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] = ''
|
|
||||||
}
|
|
||||||
mut is_config := false
|
|
||||||
if p.tok != .lcbr {
|
|
||||||
p.next()
|
|
||||||
} else {
|
|
||||||
is_config = true
|
|
||||||
}
|
|
||||||
p.check(.lcbr)
|
|
||||||
// Handle empty config ({})
|
|
||||||
if is_config && p.tok == .rcbr {
|
|
||||||
p.check(.rcbr)
|
|
||||||
p.gen('($typ) {EMPTY_STRUCT_INITIALIZATION}')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
if p.tok == .not {
|
|
||||||
// old &User{!} ==> 0 hack
|
|
||||||
p.error('use `${t.name}(0)` instead of `&$t.name{!}`')
|
|
||||||
/*
|
|
||||||
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.error('old cast syntax')
|
|
||||||
p.gen('(')
|
|
||||||
defer {
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
// Do not allow `int(my_int)`
|
|
||||||
if expr_typ == typ {
|
|
||||||
p.warn('casting `$typ` to `$expr_typ` is not needed')
|
|
||||||
}
|
|
||||||
// `face := FT_Face(cobj)` => `FT_Face face = *((FT_Face*)cobj);`
|
|
||||||
casting_voidptr_to_value := expr_typ == 'void*' && !(typ in ['int', '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 in ['byte*', '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 {
|
|
||||||
// Nothing can be cast to bool
|
|
||||||
if typ == 'bool' {
|
|
||||||
if is_number_type(expr_typ) {
|
|
||||||
p.error('cannot cast a number to `bool`')
|
|
||||||
}
|
|
||||||
p.error('cannot cast `$expr_typ` to `bool`')
|
|
||||||
}
|
|
||||||
// Strings can't be cast
|
|
||||||
if expr_typ == 'string' {
|
|
||||||
if is_number_type(typ) {
|
|
||||||
p.error('cannot cast `string` to `$typ`, use `${expr_typ}.${typ}()` instead')
|
|
||||||
}
|
|
||||||
p.error('cannot cast `$expr_typ` to `$typ`')
|
|
||||||
}
|
|
||||||
// Nothing can be cast to bool
|
|
||||||
if expr_typ == 'bool' {
|
|
||||||
p.error('cannot cast `bool` to `$typ`')
|
|
||||||
}
|
|
||||||
if typ != expr_typ && typ in p.table.sum_types {
|
|
||||||
tt := p.table.find_type(typ)
|
|
||||||
if expr_typ in tt.ctype_names {
|
|
||||||
// There is no need for a cast here, since it was already done
|
|
||||||
// in p.bool_expression, SUM TYPE CAST2 . Besides, doubling the
|
|
||||||
// cast here causes MSVC to complain with:
|
|
||||||
// error C2440: 'type cast': cannot convert from 'ExprType' to 'ExprType'
|
|
||||||
p.cgen.set_placeholder(pos, '(')
|
|
||||||
}else{
|
|
||||||
p.warn('only $tt.ctype_names can be casted to `$typ`')
|
|
||||||
p.error('cannot cast `$expr_typ` to `$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( ${parse_pointer(typ[6..])} ))'
|
|
||||||
}
|
|
||||||
// Always set pointers to 0
|
|
||||||
if typ.ends_with('*') {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
// User struct defined in another module.
|
|
||||||
if typ.contains('__') {
|
|
||||||
return '{0}'
|
|
||||||
}
|
|
||||||
if typ.ends_with('Fn') { // TODO
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
// Default values for other types are not needed because of mandatory initialization
|
|
||||||
match typ {
|
|
||||||
'bool' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'string' {
|
|
||||||
return 'tos3("")'
|
|
||||||
}
|
|
||||||
'i8' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'i16' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'i64' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'u16' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'u32' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'u64' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'byte' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'int' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'rune' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'f32' {
|
|
||||||
return '0.0'
|
|
||||||
}
|
|
||||||
'f64' {
|
|
||||||
return '0.0'
|
|
||||||
}
|
|
||||||
'byteptr' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
'voidptr' {
|
|
||||||
return '0'
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
return '{0}'
|
|
||||||
// TODO this results in
|
|
||||||
// error: expected a field designator, such as '.field = 4'
|
|
||||||
// - Empty ee= (Empty) { . = {0} } ;
|
|
||||||
/*
|
|
||||||
return match typ {
|
|
||||||
'bool'{ '0'}
|
|
||||||
'string'{ 'tos3("")'}
|
|
||||||
'i8'{ '0'}
|
|
||||||
'i16'{ '0'}
|
|
||||||
'i64'{ '0'}
|
|
||||||
'u16'{ '0'}
|
|
||||||
'u32'{ '0'}
|
|
||||||
'u64'{ '0'}
|
|
||||||
'byte'{ '0'}
|
|
||||||
'int'{ '0'}
|
|
||||||
'rune'{ '0'}
|
|
||||||
'f32'{ '0.0'}
|
|
||||||
'f64'{ '0.0'}
|
|
||||||
'byteptr'{ '0'}
|
|
||||||
'voidptr'{ '0'}
|
|
||||||
else { '{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)
|
|
||||||
p.gen('), $tmp, $elm_type)')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,260 +0,0 @@
|
||||||
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[3..] }
|
|
||||||
or_else := p.tok == .key_orelse
|
|
||||||
if or_else {
|
|
||||||
// return p.gen_handle_option_or_else(typ, name, pos)
|
|
||||||
}
|
|
||||||
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) {')
|
|
||||||
p.genln('function ${f.receiver_typ}_$name($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)
|
|
||||||
is_indexer := p.peek() == .lsbr
|
|
||||||
is_fn_call, next_expr := p.is_expr_fn_call(p.token_idx)
|
|
||||||
p.bool_expression()
|
|
||||||
if !is_indexer && !is_fn_call {
|
|
||||||
p.error_with_token_index('assigning `$next_expr` to `_` is redundant', assign_error_tok_idx)
|
|
||||||
}
|
|
||||||
or_else := p.tok == .key_orelse
|
|
||||||
if or_else {
|
|
||||||
// return p.gen_handle_option_or_else(typ, '', pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: optionals
|
|
||||||
fn (p mut Parser) gen_handle_option_or_else(_typ, name string, fn_call_ph int) string {
|
|
||||||
return _typ
|
|
||||||
}
|
|
||||||
|
|
||||||
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 IndexConfig) {
|
|
||||||
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 &Var, receiver_type string,
|
|
||||||
//ftyp string, cgen_name string, receiver Var,method_ph int)
|
|
||||||
fn (p mut Parser) gen_method_call(receiver &Var, receiver_type string,
|
|
||||||
cgen_name string, ftyp string, method_ph int)
|
|
||||||
{
|
|
||||||
// TODO js methods have been broken from the start
|
|
||||||
|
|
||||||
//mut cgen_name := p.table.fn_gen_name(f)
|
|
||||||
//mut method_call := cgen_name + '('
|
|
||||||
//p.gen('/*2*/.' + cgen_name.all_after('_') + '(')
|
|
||||||
t := receiver_type.replace('*', '')
|
|
||||||
p.cgen.set_placeholder(method_ph, '${t}_$cgen_name(')
|
|
||||||
//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[assign_pos..]
|
|
||||||
p.cgen.resetln(p.cgen.cur_line[..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
|
|
||||||
match typ {
|
|
||||||
'bool'{ return '0'}
|
|
||||||
'string'{ return 'tos("")'}
|
|
||||||
'i8'{ return '0'}
|
|
||||||
'i16'{ return '0'}
|
|
||||||
'i64'{ return '0'}
|
|
||||||
'u16'{ return '0'}
|
|
||||||
'u32'{ return '0'}
|
|
||||||
'u64'{ return '0'}
|
|
||||||
'byte'{ return '0'}
|
|
||||||
'int'{ return '0'}
|
|
||||||
'rune'{ return '0'}
|
|
||||||
'f32'{ return '0.0'}
|
|
||||||
'f64'{ return '0.0'}
|
|
||||||
'byteptr'{ return '0'}
|
|
||||||
'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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
module compiler
|
|
||||||
// import os
|
|
||||||
// import compiler.x64
|
|
|
@ -1,255 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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 (p mut Parser) get_type2() Type {
|
|
||||||
mut mul := false
|
|
||||||
mut nr_muls := 0
|
|
||||||
mut typ := ''
|
|
||||||
cat := TypeCategory.struct_
|
|
||||||
// fn type
|
|
||||||
if p.tok == .key_fn {
|
|
||||||
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 in [.name, .mul, .amp, .lsbr, .question, .lpar] {
|
|
||||||
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: .func
|
|
||||||
}
|
|
||||||
p.table.register_type(fn_typ)
|
|
||||||
return fn_typ
|
|
||||||
}
|
|
||||||
is_question := p.tok == .question
|
|
||||||
if is_question {
|
|
||||||
p.check(.question)
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiple returns
|
|
||||||
if p.tok == .lpar {
|
|
||||||
// p.warn('`()` are no longer necessary in multiple returns' +
|
|
||||||
// '\nuse `fn foo() int, int {` instead of `fn foo() (int, int) {`')
|
|
||||||
// if p.inside_tuple {p.error('unexpected (')}
|
|
||||||
// p.inside_tuple = true
|
|
||||||
p.check(.lpar)
|
|
||||||
mut types := []string
|
|
||||||
for {
|
|
||||||
types << p.get_type()
|
|
||||||
if p.tok != .comma {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.check(.comma)
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
p.check(.rpar)
|
|
||||||
// p.inside_tuple = false
|
|
||||||
typ = p.register_multi_return_stuct(types)
|
|
||||||
if is_question {
|
|
||||||
typ = stringify_pointer(typ)
|
|
||||||
typ = 'Option_$typ'
|
|
||||||
p.table.register_type_with_parent(typ, 'Option')
|
|
||||||
}
|
|
||||||
return Type{
|
|
||||||
name: typ
|
|
||||||
mod: p.mod
|
|
||||||
cat: cat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// arrays ([]int)
|
|
||||||
mut arr_level := 0
|
|
||||||
for p.tok == .lsbr {
|
|
||||||
p.check(.lsbr)
|
|
||||||
// [10]int
|
|
||||||
if p.tok == .number || (p.tok == .name && !p.inside_const) {
|
|
||||||
if p.tok == .name {
|
|
||||||
typ += '[${p.mod}__$p.lit]'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
typ += '[$p.lit]'
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
arr_level++
|
|
||||||
}
|
|
||||||
p.check(.rsbr)
|
|
||||||
}
|
|
||||||
// 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 := stringify_pointer(p.get_type()) // p.check_name()
|
|
||||||
typ = 'map_$val_type'
|
|
||||||
p.register_map(typ)
|
|
||||||
return Type{
|
|
||||||
name: typ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ptr/ref
|
|
||||||
mut warn := false
|
|
||||||
for p.tok == .mul {
|
|
||||||
if p.first_pass() {
|
|
||||||
warn = true
|
|
||||||
}
|
|
||||||
mul = true
|
|
||||||
nr_muls++
|
|
||||||
p.check(.mul)
|
|
||||||
}
|
|
||||||
if p.tok == .amp {
|
|
||||||
mul = true
|
|
||||||
nr_muls++
|
|
||||||
p.check(.amp)
|
|
||||||
}
|
|
||||||
// generic type check
|
|
||||||
ti := p.generic_dispatch.inst
|
|
||||||
if p.lit in ti.keys() {
|
|
||||||
typ += ti[p.lit]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
typ += p.lit
|
|
||||||
}
|
|
||||||
// C.Struct import
|
|
||||||
if p.lit == 'C' && p.peek() == .dot {
|
|
||||||
p.next()
|
|
||||||
p.check(.dot)
|
|
||||||
typ = p.lit
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if warn && p.mod != 'ui' {
|
|
||||||
p.warn('use `&Foo` instead of `*Foo`')
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
typ = if mod.contains('.') {
|
|
||||||
mod_gen_name(mod)
|
|
||||||
} else {
|
|
||||||
mod
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
// }
|
|
||||||
mut t_suggest,tc_suggest := p.table.find_misspelled_type(typ, p, 0.50)
|
|
||||||
if t_suggest.len > 0 {
|
|
||||||
t_suggest = '. did you mean: ($tc_suggest) `$t_suggest`'
|
|
||||||
}
|
|
||||||
econtext := if p.pref.is_debug { '('+@FILE+':'+@LINE+')' } else {''}
|
|
||||||
p.error('unknown type `$typ`$t_suggest $econtext')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if !t.is_public && t.mod != p.mod && !p.is_vgen && t.name != '' && !p.first_pass() {
|
|
||||||
p.error('type `$t.name` is private')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ == 'void' {
|
|
||||||
p.error('unknown type `$typ`')
|
|
||||||
}
|
|
||||||
if mul {
|
|
||||||
typ += strings.repeat(`*`, nr_muls)
|
|
||||||
}
|
|
||||||
// Register an []array type
|
|
||||||
if arr_level > 0 {
|
|
||||||
// p.log('ARR TYPE="$typ" run=$p.pass')
|
|
||||||
// We come across "[]User" etc ?
|
|
||||||
typ = stringify_pointer(typ)
|
|
||||||
for i := 0; i < arr_level; i++ {
|
|
||||||
typ = 'array_$typ'
|
|
||||||
}
|
|
||||||
p.register_array(typ)
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
if is_question {
|
|
||||||
typ = stringify_pointer(typ)
|
|
||||||
typ = 'Option_$typ'
|
|
||||||
p.table.register_type_with_parent(typ, 'Option')
|
|
||||||
}
|
|
||||||
// Because the code uses * to see if it's a pointer
|
|
||||||
if typ == 'byteptr' {
|
|
||||||
typ = 'byte*'
|
|
||||||
}
|
|
||||||
if typ == 'voidptr' {
|
|
||||||
// if !p.builtin_mod && p.mod != 'os' && p.mod != 'gx' && p.mod != 'gg' && !p.pref.translated {
|
|
||||||
// p.error('voidptr can only be used in unsafe code')
|
|
||||||
// }
|
|
||||||
typ = 'void*'
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
TODO this is not needed?
|
|
||||||
if typ.last_index('__') > typ.index('__') {
|
|
||||||
p.error('2 __ in gettype(): typ="$typ"')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return Type{
|
|
||||||
name: typ
|
|
||||||
cat: cat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_pointer(_typ string) string {
|
|
||||||
if !_typ.starts_with('ptr_') {
|
|
||||||
return _typ
|
|
||||||
}
|
|
||||||
mut typ := _typ.clone()
|
|
||||||
for typ.starts_with('ptr_') {
|
|
||||||
typ = typ[4..] + '*'
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stringify_pointer(typ string) string {
|
|
||||||
if !typ.ends_with('*') {
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
count := typ.count('*')
|
|
||||||
return 'ptr_'.repeat(count) + typ.trim_right('*')
|
|
||||||
}
|
|
|
@ -1,368 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
)
|
|
||||||
// Returns type if used as expression
|
|
||||||
|
|
||||||
|
|
||||||
fn (p mut Parser) match_statement(is_expr bool) string {
|
|
||||||
p.check(.key_match)
|
|
||||||
p.fspace()
|
|
||||||
is_mut := p.tok == .key_mut
|
|
||||||
if is_mut {
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
typ,expr := p.tmp_expr()
|
|
||||||
if typ.starts_with('array_') {
|
|
||||||
p.error('arrays cannot be compared')
|
|
||||||
}
|
|
||||||
is_sum_type := typ in p.table.sum_types
|
|
||||||
mut sum_child_type := ''
|
|
||||||
// is it safe to use p.cgen.insert_before ???
|
|
||||||
tmp_var := p.get_tmp()
|
|
||||||
p.cgen.insert_before('$typ $tmp_var = $expr;')
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
mut i := 0
|
|
||||||
mut all_cases_return := true
|
|
||||||
// stores typ of resulting variable
|
|
||||||
mut res_typ := ''
|
|
||||||
defer {
|
|
||||||
p.check(.rcbr)
|
|
||||||
}
|
|
||||||
for p.tok != .rcbr {
|
|
||||||
if p.tok == .key_else {
|
|
||||||
p.check(.key_else)
|
|
||||||
if p.tok == .arrow {
|
|
||||||
p.error(warn_match_arrow)
|
|
||||||
}
|
|
||||||
// unwrap match if there is only else
|
|
||||||
if i == 0 {
|
|
||||||
p.fspace()
|
|
||||||
if is_expr {
|
|
||||||
// statements are dissallowed (if match is expression) so user cant declare variables there and so on
|
|
||||||
// allow braces is else
|
|
||||||
got_brace := p.tok == .lcbr
|
|
||||||
if got_brace {
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
}
|
|
||||||
p.gen('( ')
|
|
||||||
res_typ = p.bool_expression()
|
|
||||||
p.gen(' )')
|
|
||||||
// allow braces in else
|
|
||||||
if got_brace {
|
|
||||||
p.check(.rcbr)
|
|
||||||
}
|
|
||||||
return res_typ
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.returns = false
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.genln('{ ')
|
|
||||||
p.statements()
|
|
||||||
p.returns = all_cases_return && p.returns
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is_expr {
|
|
||||||
// statements are dissallowed (if match is expression) so
|
|
||||||
// user cant declare variables there and so on
|
|
||||||
p.gen(':(')
|
|
||||||
// allow braces is else
|
|
||||||
got_brace := p.tok == .lcbr
|
|
||||||
if got_brace {
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
}
|
|
||||||
p.check_types(p.bool_expression(), res_typ)
|
|
||||||
// allow braces in else
|
|
||||||
if got_brace {
|
|
||||||
p.check(.rcbr)
|
|
||||||
}
|
|
||||||
p.gen(strings.repeat(`)`, i + 1))
|
|
||||||
return res_typ
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.returns = false
|
|
||||||
p.genln('else // default:')
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.genln('{ ')
|
|
||||||
p.statements()
|
|
||||||
p.returns = all_cases_return && p.returns
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
if is_expr {
|
|
||||||
p.gen(': (')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('else ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if is_expr {
|
|
||||||
p.gen('(')
|
|
||||||
}
|
|
||||||
if is_expr {
|
|
||||||
p.gen('(')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('if (')
|
|
||||||
}
|
|
||||||
ph := p.cgen.add_placeholder()
|
|
||||||
// Multiple checks separated by comma
|
|
||||||
p.open_scope()
|
|
||||||
mut got_comma := false
|
|
||||||
for {
|
|
||||||
if got_comma {
|
|
||||||
p.gen(') || (')
|
|
||||||
}
|
|
||||||
mut got_string := false
|
|
||||||
if typ == 'string' {
|
|
||||||
got_string = true
|
|
||||||
p.gen('string_eq($tmp_var, ')
|
|
||||||
}
|
|
||||||
else if is_sum_type {
|
|
||||||
p.gen('${tmp_var}.typ == ')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('$tmp_var == ')
|
|
||||||
}
|
|
||||||
p.expected_type = typ
|
|
||||||
// `match node { ast.BoolExpr { it := node as BoolExpr ... } }`
|
|
||||||
if is_sum_type {
|
|
||||||
sum_child_type = p.get_type2().name
|
|
||||||
tt := sum_child_type.all_after('_')
|
|
||||||
p.gen('SumType_${typ}_$tt')
|
|
||||||
// println('got child $sum_child_type')
|
|
||||||
p.register_var(Var{
|
|
||||||
name: 'it'
|
|
||||||
typ: sum_child_type+'*'
|
|
||||||
is_mut: is_mut
|
|
||||||
ptr: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.check_types(p.bool_expression(), typ)
|
|
||||||
}
|
|
||||||
p.expected_type = ''
|
|
||||||
if got_string {
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
if p.tok != .comma {
|
|
||||||
if got_comma {
|
|
||||||
p.gen(') ')
|
|
||||||
p.cgen.set_placeholder(ph, '(')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.check(.comma)
|
|
||||||
p.fspace()
|
|
||||||
got_comma = true
|
|
||||||
}
|
|
||||||
p.gen(')')
|
|
||||||
if p.tok == .arrow {
|
|
||||||
p.error(warn_match_arrow)
|
|
||||||
p.check(.arrow)
|
|
||||||
}
|
|
||||||
// statements are dissallowed (if match is expression) so user cant declare variables there and so on
|
|
||||||
if is_expr {
|
|
||||||
p.gen('? (')
|
|
||||||
// braces are required for now
|
|
||||||
p.check(.lcbr)
|
|
||||||
if i == 0 {
|
|
||||||
// on the first iteration we set value of res_typ
|
|
||||||
res_typ = p.bool_expression()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// later on we check that the value is of res_typ type
|
|
||||||
p.check_types(p.bool_expression(), res_typ)
|
|
||||||
}
|
|
||||||
// braces are required for now
|
|
||||||
p.fgen_nl()
|
|
||||||
p.check(.rcbr)
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.returns = false
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.genln('{ ')
|
|
||||||
if is_sum_type {
|
|
||||||
//p.genln(' $sum_child_type it = *($sum_child_type*)$tmp_var .obj ;')
|
|
||||||
p.genln(' $sum_child_type* it = ($sum_child_type*)${tmp_var}.obj ;')
|
|
||||||
}
|
|
||||||
p.statements()
|
|
||||||
all_cases_return = all_cases_return && p.returns
|
|
||||||
// p.gen(')')
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
p.fgen_nl()
|
|
||||||
p.close_scope()
|
|
||||||
}
|
|
||||||
p.error('match must be exhaustive')
|
|
||||||
// p.returns = false // only get here when no default, so return is not guaranteed
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) switch_statement() {
|
|
||||||
p.error('`switch` statement has been removed, use `match` instead:\n' + 'https://vlang.io/docs#match')
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) if_statement(is_expr bool, elif_depth int) string {
|
|
||||||
if is_expr {
|
|
||||||
// if p.fileis('if_expr') {
|
|
||||||
// println('IF EXPR')
|
|
||||||
// }
|
|
||||||
p.inside_if_expr = true
|
|
||||||
p.gen('((')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('if (')
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
if p.tok == .name && p.peek() == .assign {
|
|
||||||
p.error('cannot assign on if-else statement')
|
|
||||||
}
|
|
||||||
if p.tok == .name && (p.peek() == .inc || p.peek() == .dec) {
|
|
||||||
p.error('`${p.peek().str()}` is a statement')
|
|
||||||
}
|
|
||||||
// `if a := opt() { }` syntax
|
|
||||||
if p.tok == .name && p.peek() == .decl_assign {
|
|
||||||
p.check_not_reserved()
|
|
||||||
option_tmp := p.get_tmp()
|
|
||||||
var_name := p.lit
|
|
||||||
if p.known_var(var_name) {
|
|
||||||
p.error('redefinition of `$var_name`')
|
|
||||||
}
|
|
||||||
p.open_scope()
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
p.check(.decl_assign)
|
|
||||||
p.fspace()
|
|
||||||
p.is_var_decl = true
|
|
||||||
option_type,expr := p.tmp_expr() // := p.bool_expression()
|
|
||||||
if !option_type.starts_with('Option_') {
|
|
||||||
p.error('`if x := opt() {` syntax requires a function that returns an optional value')
|
|
||||||
}
|
|
||||||
p.is_var_decl = false
|
|
||||||
typ := parse_pointer(option_type[7..])
|
|
||||||
// Option_User tmp = get_user(1);
|
|
||||||
// if (tmp.ok) {
|
|
||||||
// User user = *(User*)tmp.data;
|
|
||||||
// [statements]
|
|
||||||
// }
|
|
||||||
p.cgen.insert_before('$option_type $option_tmp = $expr; ')
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.genln(option_tmp + '.ok) {')
|
|
||||||
p.genln('$typ $var_name = *($typ*) $option_tmp . data;')
|
|
||||||
p.register_var(Var{
|
|
||||||
name: var_name
|
|
||||||
typ: typ
|
|
||||||
is_mut: false // TODO
|
|
||||||
|
|
||||||
is_used: true // TODO
|
|
||||||
// is_alloc: p.is_alloc || typ.starts_with('array_')
|
|
||||||
// line_nr: p.tokens[ var_token_idx ].line_nr
|
|
||||||
// token_idx: var_token_idx
|
|
||||||
|
|
||||||
})
|
|
||||||
p.statements()
|
|
||||||
p.close_scope()
|
|
||||||
p.returns = false
|
|
||||||
if p.tok == .key_else {
|
|
||||||
p.next()
|
|
||||||
p.genln('else {')
|
|
||||||
p.check(.lcbr)
|
|
||||||
p.statements()
|
|
||||||
}
|
|
||||||
return 'void'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.check_types(p.bool_expression(), 'bool')
|
|
||||||
}
|
|
||||||
if is_expr {
|
|
||||||
p.gen(') ? (')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.genln(') {')
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
if p.inside_if_expr {
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
mut typ := ''
|
|
||||||
// if { if hack
|
|
||||||
if p.tok == .key_if && p.inside_if_expr {
|
|
||||||
typ = p.factor()
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
typ = p.statements()
|
|
||||||
}
|
|
||||||
if_returns := p.returns
|
|
||||||
p.returns = false
|
|
||||||
if p.tok == .key_else {
|
|
||||||
if p.inside_if_expr {
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
p.check(.key_else)
|
|
||||||
p.fspace()
|
|
||||||
if p.tok == .key_if {
|
|
||||||
if is_expr {
|
|
||||||
p.gen(') : (')
|
|
||||||
nested := p.if_statement(is_expr, elif_depth + 1)
|
|
||||||
nested_returns := p.returns
|
|
||||||
p.returns = if_returns && nested_returns
|
|
||||||
return nested
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen(' else ')
|
|
||||||
nested := p.if_statement(is_expr, 0)
|
|
||||||
nested_returns := p.returns
|
|
||||||
p.returns = if_returns && nested_returns
|
|
||||||
return nested
|
|
||||||
}
|
|
||||||
// return ''
|
|
||||||
}
|
|
||||||
if is_expr {
|
|
||||||
p.gen(') : (')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.genln(' else { ')
|
|
||||||
}
|
|
||||||
p.check(.lcbr)
|
|
||||||
if is_expr {
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
// statements() returns the type of the last statement
|
|
||||||
first_typ := typ
|
|
||||||
typ = p.statements()
|
|
||||||
p.inside_if_expr = false
|
|
||||||
if is_expr {
|
|
||||||
p.check_types(first_typ, typ)
|
|
||||||
p.gen(strings.repeat(`)`, 2 * (elif_depth + 1)))
|
|
||||||
}
|
|
||||||
else_returns := p.returns
|
|
||||||
p.returns = if_returns && else_returns
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
p.inside_if_expr = false
|
|
||||||
if p.fileis('test_test') {
|
|
||||||
println('if ret typ="$typ" line=$p.scanner.line_nr')
|
|
||||||
}
|
|
||||||
return typ
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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[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] ));
|
|
||||||
}
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,963 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
v.pref
|
|
||||||
v.builder
|
|
||||||
)
|
|
||||||
|
|
||||||
pub const (
|
|
||||||
Version = '0.1.25'
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
supported_platforms = ['windows', 'mac', 'macos', 'linux', 'freebsd', 'openbsd', 'netbsd',
|
|
||||||
'dragonfly', 'android', 'js', 'solaris', 'haiku', 'linux_or_macos']
|
|
||||||
)
|
|
||||||
|
|
||||||
enum Pass {
|
|
||||||
// A very short pass that only looks at imports in the beginning of
|
|
||||||
// each file
|
|
||||||
imports
|
|
||||||
// First pass, only parses and saves declarations (fn signatures,
|
|
||||||
// consts, types).
|
|
||||||
// Skips function bodies.
|
|
||||||
// We need this because in V things can be used before they are
|
|
||||||
// declared.
|
|
||||||
decl
|
|
||||||
// Second pass, parses function bodies and generates C or machine code.
|
|
||||||
main
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct V {
|
|
||||||
pub mut:
|
|
||||||
mod_file_cacher &builder.ModFileCacher // used during lookup for v.mod to support @VROOT
|
|
||||||
out_name_c string // name of the temporary C file
|
|
||||||
files []string // all V files that need to be parsed and compiled
|
|
||||||
compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .`
|
|
||||||
table &Table // table with types, vars, functions etc
|
|
||||||
cgen &CGen // C code generator
|
|
||||||
//x64 &x64.Gen
|
|
||||||
pref &pref.Preferences // all the preferences and settings extracted to a struct for reusability
|
|
||||||
parsers []Parser // file parsers
|
|
||||||
vgen_buf strings.Builder // temporary buffer for generated V code (.str() etc)
|
|
||||||
file_parser_idx map[string]int // map absolute file path to v.parsers index
|
|
||||||
gen_parser_idx map[string]int
|
|
||||||
cached_mods []string
|
|
||||||
module_lookup_paths []string
|
|
||||||
|
|
||||||
v_fmt_all bool // << input set by cmd/tools/vfmt.v
|
|
||||||
v_fmt_file string // << file given by the user from cmd/tools/vfmt.v
|
|
||||||
v_fmt_file_result string // >> file with formatted output generated by vlib/compiler/vfmt.v
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_v(pref &pref.Preferences) &V {
|
|
||||||
rdir := os.real_path(pref.path)
|
|
||||||
|
|
||||||
mut out_name_c := get_vtmp_filename(pref.out_name, '.tmp.c')
|
|
||||||
if pref.is_so {
|
|
||||||
out_name_c = get_vtmp_filename(pref.out_name, '.tmp.so.c')
|
|
||||||
}
|
|
||||||
|
|
||||||
mut vgen_buf := strings.new_builder(1000)
|
|
||||||
vgen_buf.writeln('module vgen\nimport strings')
|
|
||||||
compiled_dir:=if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
|
|
||||||
|
|
||||||
return &V{
|
|
||||||
mod_file_cacher: builder.new_mod_file_cacher()
|
|
||||||
compiled_dir:compiled_dir// if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
|
|
||||||
table: new_table(pref.obfuscate)
|
|
||||||
out_name_c: out_name_c
|
|
||||||
cgen: new_cgen(out_name_c)
|
|
||||||
//x64: x64.new_gen(out_name)
|
|
||||||
pref: pref
|
|
||||||
vgen_buf: vgen_buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called by main at the end of the compilation process, to cleanup
|
|
||||||
pub fn (v &V) finalize_compilation() {
|
|
||||||
// TODO remove
|
|
||||||
if v.pref.autofree {
|
|
||||||
/*
|
|
||||||
println('started freeing v struct')
|
|
||||||
v.table.typesmap.free()
|
|
||||||
v.table.obf_ids.free()
|
|
||||||
v.cgen.lines.free()
|
|
||||||
free(v.cgen)
|
|
||||||
for _, f in v.table.fns {
|
|
||||||
//f.local_vars.free()
|
|
||||||
f.args.free()
|
|
||||||
//f.defer_text.free()
|
|
||||||
}
|
|
||||||
v.table.fns.free()
|
|
||||||
free(v.table)
|
|
||||||
//for p in parsers {}
|
|
||||||
println('done!')
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) add_parser(parser Parser) int {
|
|
||||||
pidx := v.parsers.len
|
|
||||||
v.parsers << parser
|
|
||||||
file_path := if os.is_abs_path(parser.file_path) { parser.file_path } else { os.real_path(parser.file_path) }
|
|
||||||
v.file_parser_idx[file_path] = pidx
|
|
||||||
return pidx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v &V) get_file_parser_index(file string) ?int {
|
|
||||||
file_path := if os.is_abs_path(file) { file } else { os.real_path(file) }
|
|
||||||
if file_path in v.file_parser_idx {
|
|
||||||
return v.file_parser_idx[file_path]
|
|
||||||
}
|
|
||||||
return error('parser for "$file" not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
// find existing parser or create new one. returns v.parsers index
|
|
||||||
pub fn (v mut V) parse(file string, pass Pass) int {
|
|
||||||
// println('parse($file, $pass)')
|
|
||||||
pidx := v.get_file_parser_index(file) or {
|
|
||||||
mut p := v.new_parser_from_file(file)
|
|
||||||
p.parse(pass)
|
|
||||||
// if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
|
|
||||||
return v.add_parser(p)
|
|
||||||
}
|
|
||||||
// println('matched ' + v.parsers[pidx].file_path + ' with $file')
|
|
||||||
v.parsers[pidx].parse(pass)
|
|
||||||
// if v.parsers[i].pref.autofree { v.parsers[i].scanner.text.free() free(v.parsers[i].scanner) }
|
|
||||||
return pidx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) compile() {
|
|
||||||
//println('compile()')
|
|
||||||
// Emily: Stop people on linux from being able to build with msvc
|
|
||||||
if os.user_os() != 'windows' && v.pref.ccompiler == 'msvc' {
|
|
||||||
verror('Cannot build with msvc on ${os.user_os()}')
|
|
||||||
}
|
|
||||||
mut cgen := v.cgen
|
|
||||||
cgen.genln('// Generated by V')
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println('all .v files before:')
|
|
||||||
println(v.files)
|
|
||||||
}
|
|
||||||
v.add_v_files_to_compile()
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println('all .v files:')
|
|
||||||
println(v.files)
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if v.pref.is_debug {
|
|
||||||
println('\nparsers:')
|
|
||||||
for q in v.parsers {
|
|
||||||
println(q.file_name)
|
|
||||||
}
|
|
||||||
println('\nfiles:')
|
|
||||||
for q in v.files {
|
|
||||||
println(q)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// First pass (declarations)
|
|
||||||
for file in v.files {
|
|
||||||
v.parse(file, .decl)
|
|
||||||
}
|
|
||||||
// Main pass
|
|
||||||
cgen.pass = .main
|
|
||||||
if v.pref.is_debug {
|
|
||||||
$if js {
|
|
||||||
cgen.genln('const VDEBUG = 1;\n')
|
|
||||||
} $else {
|
|
||||||
cgen.genln('#define VDEBUG (1)')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.pref.prealloc {
|
|
||||||
cgen.genln('#define VPREALLOC (1)')
|
|
||||||
}
|
|
||||||
if v.pref.os == .js {
|
|
||||||
cgen.genln('#define _VJS (1) ')
|
|
||||||
}
|
|
||||||
v_hash := vhash()
|
|
||||||
$if js {
|
|
||||||
cgen.genln('const V_COMMIT_HASH = "$v_hash";\n')
|
|
||||||
} $else {
|
|
||||||
cgen.genln('#ifndef V_COMMIT_HASH')
|
|
||||||
cgen.genln('#define V_COMMIT_HASH "$v_hash"')
|
|
||||||
cgen.genln('#endif')
|
|
||||||
}
|
|
||||||
q := cgen.nogen // TODO hack
|
|
||||||
cgen.nogen = false
|
|
||||||
$if js {
|
|
||||||
cgen.genln(js_headers)
|
|
||||||
} $else {
|
|
||||||
if !v.pref.is_bare {
|
|
||||||
cgen.genln('#include <inttypes.h>') // int64_t etc
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cgen.genln('#include <stdint.h>')
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.pref.compile_defines_all.len > 0 {
|
|
||||||
cgen.genln('')
|
|
||||||
cgen.genln('// All custom defines : ' + v.pref.compile_defines_all.join(','))
|
|
||||||
cgen.genln('// Turned ON custom defines: ' + v.pref.compile_defines.join(','))
|
|
||||||
for cdefine in v.pref.compile_defines {
|
|
||||||
cgen.genln('#define CUSTOM_DEFINE_${cdefine}')
|
|
||||||
}
|
|
||||||
cgen.genln('//')
|
|
||||||
cgen.genln('')
|
|
||||||
}
|
|
||||||
|
|
||||||
cgen.genln(c_builtin_types)
|
|
||||||
|
|
||||||
if !v.pref.is_bare {
|
|
||||||
cgen.genln(c_headers)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cgen.genln(bare_c_headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.generate_hotcode_reloading_declarations()
|
|
||||||
// We need the cjson header for all the json decoding that will be done in
|
|
||||||
// default mode
|
|
||||||
imports_json := 'json' in v.table.imports
|
|
||||||
if v.pref.build_mode == .default_mode {
|
|
||||||
if imports_json {
|
|
||||||
cgen.genln('#include "cJSON.h"')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.pref.build_mode == .default_mode {
|
|
||||||
// If we declare these for all modes, then when running `v a.v` we'll get
|
|
||||||
// `/usr/bin/ld: multiple definition of 'total_m'`
|
|
||||||
$if !js {
|
|
||||||
cgen.genln('int g_test_oks = 0;')
|
|
||||||
cgen.genln('int g_test_fails = 0;')
|
|
||||||
}
|
|
||||||
if imports_json {
|
|
||||||
cgen.genln('
|
|
||||||
#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key))
|
|
||||||
')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if '-debug_alloc' in os.args {
|
|
||||||
cgen.genln('#define DEBUG_ALLOC 1')
|
|
||||||
}
|
|
||||||
if v.pref.is_live && v.pref.os != .windows {
|
|
||||||
cgen.includes << '#include <dlfcn.h>'
|
|
||||||
}
|
|
||||||
// cgen.genln('/*================================== FNS =================================*/')
|
|
||||||
cgen.genln('// this line will be replaced with definitions')
|
|
||||||
mut defs_pos := cgen.lines.len - 1
|
|
||||||
if defs_pos == -1 {
|
|
||||||
defs_pos = 0
|
|
||||||
}
|
|
||||||
cgen.nogen = q
|
|
||||||
for i, file in v.files {
|
|
||||||
v.parse(file, .main)
|
|
||||||
// if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
|
|
||||||
// Format all files (don't format automatically generated vlib headers)
|
|
||||||
// if !v.pref.nofmt && !file.contains('/vlib/') {
|
|
||||||
// new vfmt is not ready yet
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
// add parser generated V code (str() methods etc)
|
|
||||||
mut vgen_parser := v.new_parser_from_string(v.vgen_buf.str())
|
|
||||||
// free the string builder which held the generated methods
|
|
||||||
v.vgen_buf.free()
|
|
||||||
vgen_parser.is_vgen = true
|
|
||||||
// v.add_parser(vgen_parser)
|
|
||||||
vgen_parser.parse(.main)
|
|
||||||
// Generate .vh if we are building a module
|
|
||||||
if v.pref.build_mode == .build_module {
|
|
||||||
//generate_vh(v.pref.path)
|
|
||||||
}
|
|
||||||
// All definitions
|
|
||||||
mut def := strings.new_builder(10000) // Avoid unnecessary allocations
|
|
||||||
def.writeln(cgen.const_defines.join_lines())
|
|
||||||
$if !js {
|
|
||||||
def.writeln(cgen.includes.join_lines())
|
|
||||||
def.writeln(cgen.typedefs.join_lines())
|
|
||||||
def.writeln(v.type_definitions())
|
|
||||||
if !v.pref.is_bare {
|
|
||||||
def.writeln('\nstring _STR(const char*, ...);\n')
|
|
||||||
def.writeln('\nstring _STR_TMP(const char*, ...);\n')
|
|
||||||
}
|
|
||||||
def.writeln(cgen.fns.join_lines()) // fn definitions
|
|
||||||
def.writeln(v.interface_table())
|
|
||||||
} $else {
|
|
||||||
def.writeln(v.type_definitions())
|
|
||||||
}
|
|
||||||
def.writeln(cgen.consts.join_lines())
|
|
||||||
def.writeln(cgen.thread_args.join_lines())
|
|
||||||
if v.pref.is_prof {
|
|
||||||
def.writeln('; // Prof counters:')
|
|
||||||
def.writeln(v.prof_counters())
|
|
||||||
}
|
|
||||||
cgen.lines[defs_pos] = def.str()
|
|
||||||
v.generate_init()
|
|
||||||
v.generate_main()
|
|
||||||
v.generate_hot_reload_code()
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
v.log('flags=')
|
|
||||||
for flag in v.get_os_cflags() {
|
|
||||||
println(' * ' + flag.format())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$if js {
|
|
||||||
cgen.genln('main__main();')
|
|
||||||
}
|
|
||||||
cgen.save()
|
|
||||||
v.cc()
|
|
||||||
//println(v.table.imports)
|
|
||||||
//println(v.table.modules)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) compile2() {
|
|
||||||
if os.user_os() != 'windows' && v.pref.ccompiler == 'msvc' {
|
|
||||||
verror('Cannot build with msvc on ${os.user_os()}')
|
|
||||||
}
|
|
||||||
//cgen.genln('// Generated by V')
|
|
||||||
//println('compile2()')
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println('all .v files before:')
|
|
||||||
println(v.files)
|
|
||||||
}
|
|
||||||
// v1 compiler files
|
|
||||||
//v.add_v_files_to_compile()
|
|
||||||
//v.files << v.dir
|
|
||||||
// v2 compiler
|
|
||||||
v.files << v.get_builtin_files()
|
|
||||||
v.files << v.get_user_files()
|
|
||||||
v.set_module_lookup_paths()
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println('all .v files:')
|
|
||||||
println(v.files)
|
|
||||||
}
|
|
||||||
mut b := v.new_v2()
|
|
||||||
b.build_c(v.files, v.out_name_c)// v.pref.out_name + '.c')
|
|
||||||
v.cc()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) compile_x64() {
|
|
||||||
$if !linux {
|
|
||||||
println('v -x64 can only generate Linux binaries for now')
|
|
||||||
println('You are not on a Linux system, so you will not ' + 'be able to run the resulting executable')
|
|
||||||
}
|
|
||||||
//v.files << v.v_files_from_dir(os.join_path(v.pref.vlib_path,'builtin','bare'))
|
|
||||||
v.files << v.pref.path
|
|
||||||
v.set_module_lookup_paths()
|
|
||||||
mut b := v.new_v2()
|
|
||||||
// move all this logic to v2
|
|
||||||
b.build_x64(v.files, v.pref.out_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make v2 from v1
|
|
||||||
fn (v &V) new_v2() builder.Builder {
|
|
||||||
mut b := builder.new_builder(v.pref)
|
|
||||||
b = { b|
|
|
||||||
os: v.pref.os,
|
|
||||||
module_path: v_modules_path,
|
|
||||||
compiled_dir: v.compiled_dir,
|
|
||||||
module_search_paths: v.module_lookup_paths
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (v mut V) generate_init() {
|
|
||||||
$if js {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if v.pref.build_mode == .build_module {
|
|
||||||
nogen := v.cgen.nogen
|
|
||||||
v.cgen.nogen = false
|
|
||||||
consts_init_body := v.cgen.consts_init.join_lines()
|
|
||||||
init_fn_name := mod_gen_name(v.pref.mod) + '__init_consts'
|
|
||||||
v.cgen.genln('void ${init_fn_name}();\nvoid ${init_fn_name}() {\n$consts_init_body\n}')
|
|
||||||
v.cgen.nogen = nogen
|
|
||||||
}
|
|
||||||
if v.pref.build_mode == .default_mode {
|
|
||||||
mut call_mod_init := ''
|
|
||||||
mut call_mod_init_consts := ''
|
|
||||||
if 'builtin' in v.cached_mods {
|
|
||||||
v.cgen.genln('void builtin__init_consts();')
|
|
||||||
call_mod_init_consts += 'builtin__init_consts();\n'
|
|
||||||
}
|
|
||||||
for mod in v.table.imports {
|
|
||||||
init_fn_name := mod_gen_name(mod) + '__init'
|
|
||||||
if v.table.known_fn(init_fn_name) {
|
|
||||||
call_mod_init += '${init_fn_name}();\n'
|
|
||||||
}
|
|
||||||
if mod in v.cached_mods {
|
|
||||||
v.cgen.genln('void ${init_fn_name}_consts();')
|
|
||||||
call_mod_init_consts += '${init_fn_name}_consts();\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
consts_init_body := v.cgen.consts_init.join_lines()
|
|
||||||
if v.pref.is_bare {
|
|
||||||
// vlib can't have init_consts()
|
|
||||||
v.cgen.genln('
|
|
||||||
void init() {
|
|
||||||
$call_mod_init_consts
|
|
||||||
$consts_init_body
|
|
||||||
builtin__init();
|
|
||||||
$call_mod_init
|
|
||||||
}
|
|
||||||
')
|
|
||||||
}
|
|
||||||
if !v.pref.is_bare && !v.pref.is_so {
|
|
||||||
// vlib can't have `init_consts()`
|
|
||||||
v.cgen.genln('void init() {
|
|
||||||
#if VPREALLOC
|
|
||||||
g_m2_buf = malloc(50 * 1000 * 1000);
|
|
||||||
g_m2_ptr = g_m2_buf;
|
|
||||||
puts("allocated 50 mb");
|
|
||||||
#endif
|
|
||||||
$call_mod_init_consts
|
|
||||||
$consts_init_body
|
|
||||||
builtin__init();
|
|
||||||
$call_mod_init
|
|
||||||
}')
|
|
||||||
}
|
|
||||||
if !v.pref.is_bare {
|
|
||||||
v.generate_str_definitions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) generate_main() {
|
|
||||||
mut cgen := v.cgen
|
|
||||||
$if js {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if v.pref.is_vlines {
|
|
||||||
// After this point, the v files are compiled.
|
|
||||||
// The rest is auto generated code, which will not have
|
|
||||||
// different .v source file/line numbers.
|
|
||||||
lines_so_far := cgen.lines.join('\n').count('\n') + 5
|
|
||||||
cgen.genln('')
|
|
||||||
cgen.genln('// Reset the file/line numbers')
|
|
||||||
cgen.lines << '#line $lines_so_far "${cescaped_path(os.real_path(cgen.out_path))}"'
|
|
||||||
cgen.genln('')
|
|
||||||
}
|
|
||||||
// Make sure the main function exists
|
|
||||||
// Obviously we don't need it in libraries
|
|
||||||
if v.pref.build_mode != .build_module {
|
|
||||||
if !v.table.main_exists() && !v.pref.is_test {
|
|
||||||
// It can be skipped in single file programs
|
|
||||||
// But make sure that there's some code outside of main()
|
|
||||||
if (v.pref.is_script && cgen.fn_main.trim_space() != '') || v.pref.is_repl {
|
|
||||||
// println('Generating main()...')
|
|
||||||
v.gen_main_start(true)
|
|
||||||
cgen.genln('$cgen.fn_main;')
|
|
||||||
v.gen_main_end('return 0')
|
|
||||||
}
|
|
||||||
else if v.v_fmt_file=='' && !v.pref.is_repl && !v.pref.is_so {
|
|
||||||
verror('function `main` is not declared in the main module\nPlease add: \nfn main(){\n}\n... to your main program .v file, and try again.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if v.pref.is_test {
|
|
||||||
if v.table.main_exists() {
|
|
||||||
verror('test files cannot have function `main`')
|
|
||||||
}
|
|
||||||
test_fn_names := v.table.all_test_function_names()
|
|
||||||
if test_fn_names.len == 0 {
|
|
||||||
verror('test files need to have at least one test function')
|
|
||||||
}
|
|
||||||
// Generate a C `main`, which calls every single test function
|
|
||||||
v.gen_main_start(false)
|
|
||||||
if v.pref.is_stats {
|
|
||||||
cgen.genln('BenchedTests bt = main__start_testing(${test_fn_names.len},tos3("$v.pref.path"));')
|
|
||||||
}
|
|
||||||
for tfname in test_fn_names {
|
|
||||||
if v.pref.is_stats {
|
|
||||||
cgen.genln('BenchedTests_testing_step_start(&bt, tos3("$tfname"));')
|
|
||||||
}
|
|
||||||
cgen.genln('${tfname}();')
|
|
||||||
if v.pref.is_stats {
|
|
||||||
cgen.genln('BenchedTests_testing_step_end(&bt);')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.pref.is_stats {
|
|
||||||
cgen.genln('BenchedTests_end_testing(&bt);')
|
|
||||||
}
|
|
||||||
v.gen_main_end('return g_test_fails > 0')
|
|
||||||
}
|
|
||||||
else if v.table.main_exists() && !v.pref.is_so {
|
|
||||||
v.gen_main_start(true)
|
|
||||||
cgen.genln(' main__main();')
|
|
||||||
if !v.pref.is_bare {
|
|
||||||
cgen.genln('#if VPREALLOC')
|
|
||||||
cgen.genln('free(g_m2_buf);')
|
|
||||||
cgen.genln('puts("freed mem buf");')
|
|
||||||
cgen.genln('#endif')
|
|
||||||
}
|
|
||||||
v.gen_main_end('return 0')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) gen_main_start(add_os_args bool) {
|
|
||||||
if v.pref.os == .windows {
|
|
||||||
if 'glfw' in v.table.imports {
|
|
||||||
// GUI application
|
|
||||||
v.cgen.genln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd) { ')
|
|
||||||
v.cgen.genln(' typedef LPWSTR*(WINAPI *cmd_line_to_argv)(LPCWSTR, int*);')
|
|
||||||
v.cgen.genln(' HMODULE shell32_module = LoadLibrary(L"shell32.dll");')
|
|
||||||
v.cgen.genln(' cmd_line_to_argv CommandLineToArgvW = (cmd_line_to_argv)GetProcAddress(shell32_module, "CommandLineToArgvW");')
|
|
||||||
v.cgen.genln(' int argc;')
|
|
||||||
v.cgen.genln(' wchar_t** argv = CommandLineToArgvW(cmd_line, &argc);')
|
|
||||||
} else {
|
|
||||||
// Console application
|
|
||||||
v.cgen.genln('int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { ')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.cgen.genln('int main(int argc, char** argv) { ')
|
|
||||||
}
|
|
||||||
v.cgen.genln(' init();')
|
|
||||||
if add_os_args && 'os' in v.table.imports {
|
|
||||||
if v.pref.os == .windows {
|
|
||||||
v.cgen.genln(' os__args = os__init_os_args_wide(argc, argv);')
|
|
||||||
} else {
|
|
||||||
v.cgen.genln(' os__args = os__init_os_args(argc, (byteptr*)argv);')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.generate_hotcode_reloading_main_caller()
|
|
||||||
v.cgen.genln('')
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) gen_main_end(return_statement string) {
|
|
||||||
v.cgen.genln('')
|
|
||||||
v.cgen.genln(' $return_statement;')
|
|
||||||
v.cgen.genln('}')
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v &V) v_files_from_dir(dir string) []string {
|
|
||||||
mut res := []string
|
|
||||||
if !os.exists(dir) {
|
|
||||||
if dir == 'compiler' && os.is_dir('vlib') {
|
|
||||||
println('looks like you are trying to build V with an old command')
|
|
||||||
println('use `v -o v cmd/v` instead of `v -o v compiler`')
|
|
||||||
}
|
|
||||||
verror("$dir doesn't exist")
|
|
||||||
}
|
|
||||||
else if !os.is_dir(dir) {
|
|
||||||
verror("$dir isn't a directory!")
|
|
||||||
}
|
|
||||||
mut files := os.ls(dir)or{
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println('v_files_from_dir ("$dir")')
|
|
||||||
}
|
|
||||||
files.sort()
|
|
||||||
for file in files {
|
|
||||||
if !file.ends_with('.v') && !file.ends_with('.vh') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_test.v') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (file.ends_with('_win.v') || file.ends_with('_windows.v')) && v.pref.os != .windows {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (file.ends_with('_lin.v') || file.ends_with('_linux.v')) && v.pref.os != .linux {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (file.ends_with('_mac.v') || file.ends_with('_darwin.v')) && v.pref.os != .mac {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_nix.v') && v.pref.os == .windows {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_android.v') && v.pref.os != .android {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_freebsd.v') && v.pref.os != .freebsd {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_solaris.v') && v.pref.os != .solaris {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_js.v') && v.pref.os != .js {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if file.ends_with('_c.v') && v.pref.os == .js {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v.pref.compile_defines_all.len > 0 && file.contains('_d_') {
|
|
||||||
mut allowed := false
|
|
||||||
for cdefine in v.pref.compile_defines {
|
|
||||||
file_postfix := '_d_${cdefine}.v'
|
|
||||||
if file.ends_with(file_postfix) {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !allowed {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res << os.join_path(dir, file)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses imports, adds necessary libs, and then user files
|
|
||||||
pub fn (v mut V) add_v_files_to_compile() {
|
|
||||||
v.set_module_lookup_paths()
|
|
||||||
mut builtin_files := v.get_builtin_files()
|
|
||||||
if v.pref.is_bare {
|
|
||||||
// builtin_files = []
|
|
||||||
}
|
|
||||||
// Builtin cache exists? Use it.
|
|
||||||
if v.pref.is_cache {
|
|
||||||
builtin_vh := os.join_path(v_modules_path, 'vlib', 'builtin.vh')
|
|
||||||
if os.exists(builtin_vh) {
|
|
||||||
v.cached_mods << 'builtin'
|
|
||||||
builtin_files = [builtin_vh]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
v.log('v.add_v_files_to_compile > builtin_files: $builtin_files')
|
|
||||||
}
|
|
||||||
// Parse builtin imports
|
|
||||||
for file in builtin_files {
|
|
||||||
// add builtins first
|
|
||||||
v.files << file
|
|
||||||
mut p := v.new_parser_from_file(file)
|
|
||||||
p.parse(.imports)
|
|
||||||
// if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
|
|
||||||
v.add_parser(p)
|
|
||||||
}
|
|
||||||
// Parse user imports
|
|
||||||
for file in v.get_user_files() {
|
|
||||||
mut p := v.new_parser_from_file(file)
|
|
||||||
p.parse(.imports)
|
|
||||||
if p.v_script {
|
|
||||||
v.log('imports0:')
|
|
||||||
println(v.table.imports)
|
|
||||||
println(v.files)
|
|
||||||
p.register_import('os', 0)
|
|
||||||
p.table.imports << 'os'
|
|
||||||
p.table.register_module('os')
|
|
||||||
}
|
|
||||||
// if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
|
|
||||||
v.add_parser(p)
|
|
||||||
}
|
|
||||||
// Parse lib imports
|
|
||||||
v.parse_lib_imports()
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
v.log('imports:')
|
|
||||||
println(v.table.imports)
|
|
||||||
}
|
|
||||||
// resolve deps and add imports in correct order
|
|
||||||
imported_mods := v.resolve_deps().imports()
|
|
||||||
for mod in imported_mods {
|
|
||||||
if mod == 'builtin' || mod == 'main' {
|
|
||||||
// builtin already added
|
|
||||||
// main files will get added last
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// use cached built module if exists
|
|
||||||
// Cached modules are broken currently
|
|
||||||
/*
|
|
||||||
if v.pref.vpath != '' && v.pref.build_mode != .build_module && !mod.contains('vweb') {
|
|
||||||
mod_path := mod.replace('.', os.path_separator)
|
|
||||||
vh_path := '$v_modules_path${os.path_separator}vlib${os.path_separator}${mod_path}.vh'
|
|
||||||
if v.pref.is_cache && os.exists(vh_path) {
|
|
||||||
eprintln('using cached module `$mod`: $vh_path')
|
|
||||||
v.cached_mods << mod
|
|
||||||
v.files << vh_path
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// standard module
|
|
||||||
vfiles := v.get_imported_module_files(mod)
|
|
||||||
for file in vfiles {
|
|
||||||
v.files << file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add remaining main files last
|
|
||||||
for p in v.parsers {
|
|
||||||
if p.mod != 'main' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if p.is_vgen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
v.files << p.file_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v &V) get_builtin_files() []string {
|
|
||||||
// Lookup for built-in folder in lookup path.
|
|
||||||
// Assumption: `builtin/` folder implies usable implementation of builtin
|
|
||||||
for location in v.pref.lookup_path {
|
|
||||||
if !os.exists(os.join_path(location, 'builtin')) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v.pref.is_bare {
|
|
||||||
return v.v_files_from_dir(os.join_path(location, 'builtin', 'bare'))
|
|
||||||
}
|
|
||||||
$if js {
|
|
||||||
return v.v_files_from_dir(os.join_path(location, 'builtin', 'js'))
|
|
||||||
}
|
|
||||||
return v.v_files_from_dir(os.join_path(location, 'builtin'))
|
|
||||||
}
|
|
||||||
// Panic. We couldn't find the folder.
|
|
||||||
verror('`builtin/` not included on module lookup path.
|
|
||||||
Did you forget to add vlib to the path? (Use @vlib for default vlib)')
|
|
||||||
panic('Unreachable code reached.')
|
|
||||||
}
|
|
||||||
|
|
||||||
// get user files
|
|
||||||
pub fn (v &V) get_user_files() []string {
|
|
||||||
mut dir := v.pref.path
|
|
||||||
v.log('get_v_files($dir)')
|
|
||||||
// Need to store user files separately, because they have to be added after
|
|
||||||
// libs, but we dont know which libs need to be added yet
|
|
||||||
mut user_files := []string
|
|
||||||
|
|
||||||
// See cmd/tools/preludes/README.md for more info about what preludes are
|
|
||||||
vroot := os.dir(pref.vexe_path())
|
|
||||||
preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes')
|
|
||||||
if v.pref.is_live {
|
|
||||||
user_files << os.join_path(preludes_path, 'live_main.v')
|
|
||||||
}
|
|
||||||
if v.pref.is_solive {
|
|
||||||
user_files << os.join_path(preludes_path, 'live_shared.v')
|
|
||||||
}
|
|
||||||
if v.pref.is_test {
|
|
||||||
user_files << os.join_path(preludes_path, 'tests_assertions.v')
|
|
||||||
}
|
|
||||||
if v.pref.is_test && v.pref.is_stats {
|
|
||||||
user_files << os.join_path(preludes_path, 'tests_with_stats.v')
|
|
||||||
}
|
|
||||||
|
|
||||||
is_test := dir.ends_with('_test.v')
|
|
||||||
mut is_internal_module_test := false
|
|
||||||
if is_test {
|
|
||||||
tcontent := os.read_file(dir)or{
|
|
||||||
panic('$dir does not exist')
|
|
||||||
}
|
|
||||||
slines := tcontent.trim_space().split_into_lines()
|
|
||||||
for sline in slines {
|
|
||||||
line := sline.trim_space()
|
|
||||||
if line.len > 2 {
|
|
||||||
if line[0] == `/` && line[1] == `/` {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if line.starts_with('module ') && !line.starts_with('module main') {
|
|
||||||
is_internal_module_test = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is_internal_module_test {
|
|
||||||
// v volt/slack_test.v: compile all .v files to get the environment
|
|
||||||
single_test_v_file := os.real_path(dir)
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
v.log('> Compiling an internal module _test.v file $single_test_v_file .')
|
|
||||||
v.log('> That brings in all other ordinary .v files in the same module too .')
|
|
||||||
}
|
|
||||||
user_files << single_test_v_file
|
|
||||||
dir = os.base_dir(single_test_v_file)
|
|
||||||
}
|
|
||||||
is_real_file := os.exists(dir) && !os.is_dir(dir)
|
|
||||||
if is_real_file && ( dir.ends_with('.v') || dir.ends_with('.vsh') ) {
|
|
||||||
single_v_file := dir
|
|
||||||
// Just compile one file and get parent dir
|
|
||||||
user_files << single_v_file
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
v.log('> just compile one file: "${single_v_file}"')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
v.log('> add all .v files from directory "${dir}" ...')
|
|
||||||
}
|
|
||||||
// Add .v files from the directory being compiled
|
|
||||||
files := v.v_files_from_dir(dir)
|
|
||||||
for file in files {
|
|
||||||
user_files << file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if user_files.len == 0 {
|
|
||||||
println('No input .v files')
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
v.log('user_files: $user_files')
|
|
||||||
}
|
|
||||||
return user_files
|
|
||||||
}
|
|
||||||
|
|
||||||
// get module files from already parsed imports
|
|
||||||
fn (v &V) get_imported_module_files(mod string) []string {
|
|
||||||
mut files := []string
|
|
||||||
for p in v.parsers {
|
|
||||||
if p.mod == mod {
|
|
||||||
files << p.file_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse deps from already parsed builtin/user files
|
|
||||||
pub fn (v mut V) parse_lib_imports() {
|
|
||||||
mut done_imports := []string
|
|
||||||
for i in 0 .. v.parsers.len {
|
|
||||||
for _, mod in v.parsers[i].import_table.imports {
|
|
||||||
if mod in done_imports {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
import_path := v.parsers[i].find_module_path(mod) or {
|
|
||||||
v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)\n$err', v.parsers[i].import_table.get_import_tok_idx(mod))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
vfiles := v.v_files_from_dir(import_path)
|
|
||||||
if vfiles.len == 0 {
|
|
||||||
v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_table.get_import_tok_idx(mod))
|
|
||||||
}
|
|
||||||
// Add all imports referenced by these libs
|
|
||||||
for file in vfiles {
|
|
||||||
pidx := v.parse(file, .imports)
|
|
||||||
p_mod := v.parsers[pidx].mod
|
|
||||||
if p_mod != mod {
|
|
||||||
v.parsers[pidx].error_with_token_index('bad module definition: ${v.parsers[pidx].file_path} imports module "$mod" but $file is defined as module `$p_mod`', 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done_imports << mod
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn (v &V) log(s string) {
|
|
||||||
if !v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
println(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verror(s string) {
|
|
||||||
println('V error: $s')
|
|
||||||
os.flush()
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vhash() string {
|
|
||||||
mut buf := [50]byte
|
|
||||||
buf[0] = 0
|
|
||||||
C.snprintf(charptr(buf), 50, '%s', C.V_COMMIT_HASH)
|
|
||||||
return tos_clone(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cescaped_path(s string) string {
|
|
||||||
return s.replace('\\', '\\\\')
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn os_from_string(os string) pref.OS {
|
|
||||||
match os {
|
|
||||||
'linux' {
|
|
||||||
return .linux
|
|
||||||
}
|
|
||||||
'windows' {
|
|
||||||
return .windows
|
|
||||||
}
|
|
||||||
'mac' {
|
|
||||||
return .mac
|
|
||||||
}
|
|
||||||
'macos' {
|
|
||||||
return .mac
|
|
||||||
}
|
|
||||||
'freebsd' {
|
|
||||||
return .freebsd
|
|
||||||
}
|
|
||||||
'openbsd' {
|
|
||||||
return .openbsd
|
|
||||||
}
|
|
||||||
'netbsd' {
|
|
||||||
return .netbsd
|
|
||||||
}
|
|
||||||
'dragonfly' {
|
|
||||||
return .dragonfly
|
|
||||||
}
|
|
||||||
'js' {
|
|
||||||
return .js
|
|
||||||
}
|
|
||||||
'solaris' {
|
|
||||||
return .solaris
|
|
||||||
}
|
|
||||||
'android' {
|
|
||||||
return .android
|
|
||||||
}
|
|
||||||
'msvc' {
|
|
||||||
// notice that `-os msvc` became `-cc msvc`
|
|
||||||
verror('use the flag `-cc msvc` to build using msvc')
|
|
||||||
}
|
|
||||||
'haiku' {
|
|
||||||
return .haiku
|
|
||||||
}
|
|
||||||
'linux_or_macos' {
|
|
||||||
return .linux
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
panic('bad os $os')
|
|
||||||
}}
|
|
||||||
// println('bad os $os') // todo panic?
|
|
||||||
return .linux
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
pub fn set_vroot_folder(vroot_path string) {
|
|
||||||
// Preparation for the compiler module:
|
|
||||||
// VEXE env variable is needed so that compiler.vexe_path()
|
|
||||||
// can return it later to whoever needs it:
|
|
||||||
vname := if os.user_os() == 'windows' { 'v.exe' } else { 'v' }
|
|
||||||
os.setenv('VEXE', os.real_path([vroot_path, vname].join(os.path_separator)), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (v mut V) generate_str_definitions() {
|
|
||||||
// _STR function can't be defined in vlib
|
|
||||||
v.cgen.genln('
|
|
||||||
string _STR(const char *fmt, ...) {
|
|
||||||
va_list argptr;
|
|
||||||
va_start(argptr, fmt);
|
|
||||||
size_t len = vsnprintf(0, 0, fmt, argptr) + 1;
|
|
||||||
va_end(argptr);
|
|
||||||
byte* buf = malloc(len);
|
|
||||||
va_start(argptr, fmt);
|
|
||||||
vsprintf((char *)buf, fmt, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
#ifdef DEBUG_ALLOC
|
|
||||||
puts("_STR:");
|
|
||||||
puts(buf);
|
|
||||||
#endif
|
|
||||||
return tos2(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
string _STR_TMP(const char *fmt, ...) {
|
|
||||||
va_list argptr;
|
|
||||||
va_start(argptr, fmt);
|
|
||||||
//size_t len = vsnprintf(0, 0, fmt, argptr) + 1;
|
|
||||||
va_end(argptr);
|
|
||||||
va_start(argptr, fmt);
|
|
||||||
vsprintf((char *)g_str_buf, fmt, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
#ifdef DEBUG_ALLOC
|
|
||||||
//puts("_STR_TMP:");
|
|
||||||
//puts(g_str_buf);
|
|
||||||
#endif
|
|
||||||
return tos2(g_str_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
')
|
|
||||||
}
|
|
|
@ -1,219 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
v.pref
|
|
||||||
)
|
|
||||||
|
|
||||||
pub const (
|
|
||||||
v_modules_path = pref.default_module_path
|
|
||||||
)
|
|
||||||
// Holds import information scoped to the parsed file
|
|
||||||
struct ImportTable {
|
|
||||||
mut:
|
|
||||||
imports map[string]string // alias => module
|
|
||||||
used_imports []string // alias
|
|
||||||
import_tok_idx map[string]int // module => idx
|
|
||||||
}
|
|
||||||
// Once we have a module format we can read from module file instead
|
|
||||||
// this is not optimal
|
|
||||||
fn (table &Table) qualify_module(mod string, file_path string) string {
|
|
||||||
for m in table.imports {
|
|
||||||
if m.contains('.') && m.contains(mod) {
|
|
||||||
m_parts := m.split('.')
|
|
||||||
m_path := m_parts.join(os.path_separator)
|
|
||||||
if mod == m_parts[m_parts.len - 1] && file_path.contains(m_path) {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mod
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_import_table() ImportTable {
|
|
||||||
return ImportTable{
|
|
||||||
imports: map[string]string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) register_import(mod string, tok_idx int) {
|
|
||||||
p.register_import_alias(mod, mod, tok_idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) register_import_alias(alias string, mod string, tok_idx int) {
|
|
||||||
// NOTE: come back here
|
|
||||||
// if alias in it.imports && it.imports[alias] == mod {}
|
|
||||||
if alias in p.import_table.imports && p.import_table.imports[alias] != mod {
|
|
||||||
p.error('cannot import $mod as $alias: import name $alias already in use')
|
|
||||||
}
|
|
||||||
if mod.contains('.internal.') && !p.is_vgen {
|
|
||||||
mod_parts := mod.split('.')
|
|
||||||
mut internal_mod_parts := []string
|
|
||||||
for part in mod_parts {
|
|
||||||
if part == 'internal' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
internal_mod_parts << part
|
|
||||||
}
|
|
||||||
internal_parent := internal_mod_parts.join('.')
|
|
||||||
if !p.mod.starts_with(internal_parent) {
|
|
||||||
p.error('module $mod can only be imported internally by libs')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.import_table.imports[alias] = mod
|
|
||||||
p.import_table.import_tok_idx[mod] = tok_idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it &ImportTable) get_import_tok_idx(mod string) int {
|
|
||||||
return it.import_tok_idx[mod]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it &ImportTable) known_import(mod string) bool {
|
|
||||||
return mod in it.imports || it.is_aliased(mod)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it &ImportTable) known_alias(alias string) bool {
|
|
||||||
return alias in it.imports
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it &ImportTable) is_aliased(mod string) bool {
|
|
||||||
for _, val in it.imports {
|
|
||||||
if val == mod {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it &ImportTable) resolve_alias(alias string) string {
|
|
||||||
return it.imports[alias]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it mut ImportTable) register_used_import(alias string) {
|
|
||||||
if !(alias in it.used_imports) {
|
|
||||||
it.used_imports << alias
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (it &ImportTable) is_used_import(alias string) bool {
|
|
||||||
return alias in it.used_imports
|
|
||||||
}
|
|
||||||
|
|
||||||
// should module be accessable
|
|
||||||
pub fn (p &Parser) is_mod_in_scope(mod string) bool {
|
|
||||||
mut mods_in_scope := ['', 'builtin', 'main', p.mod]
|
|
||||||
for _, m in p.import_table.imports {
|
|
||||||
mods_in_scope << m
|
|
||||||
}
|
|
||||||
return mod in mods_in_scope
|
|
||||||
}
|
|
||||||
|
|
||||||
// return resolved dep graph (order deps)
|
|
||||||
pub fn (v &V) resolve_deps() &DepGraph {
|
|
||||||
graph := v.import_graph()
|
|
||||||
deps_resolved := graph.resolve()
|
|
||||||
if !deps_resolved.acyclic {
|
|
||||||
verror('import cycle detected between the following modules: \n' + deps_resolved.display_cycles())
|
|
||||||
}
|
|
||||||
return deps_resolved
|
|
||||||
}
|
|
||||||
|
|
||||||
// graph of all imported modules
|
|
||||||
pub fn (v &V) import_graph() &DepGraph {
|
|
||||||
mut graph := new_dep_graph()
|
|
||||||
for p in v.parsers {
|
|
||||||
mut deps := []string
|
|
||||||
for _, m in p.import_table.imports {
|
|
||||||
deps << m
|
|
||||||
}
|
|
||||||
graph.add(p.mod, deps)
|
|
||||||
}
|
|
||||||
return graph
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
[inline]
|
|
||||||
fn (v &V) module_path(mod string) string {
|
|
||||||
// submodule support
|
|
||||||
return mod.replace('.', os.path_separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'strings' => 'VROOT/vlib/strings'
|
|
||||||
// 'installed_mod' => '~/.vmodules/installed_mod'
|
|
||||||
// 'local_mod' => '/path/to/current/dir/local_mod'
|
|
||||||
fn (v mut V) set_module_lookup_paths() {
|
|
||||||
// Module search order:
|
|
||||||
// 0) V test files are very commonly located right inside the folder of the
|
|
||||||
// module, which they test. Adding the parent folder of the module folder
|
|
||||||
// with the _test.v files, *guarantees* that the tested module can be found
|
|
||||||
// without needing to set custom options/flags.
|
|
||||||
// 1) search in the *same* directory, as the compiled final v program source
|
|
||||||
// (i.e. the . in `v .` or file.v in `v file.v`)
|
|
||||||
// 2) search in the modules/ in the same directory.
|
|
||||||
// 3) search in the provided paths
|
|
||||||
// By default, these are what (3) contains:
|
|
||||||
// 3.1) search in vlib/
|
|
||||||
// 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm)
|
|
||||||
v.module_lookup_paths = []
|
|
||||||
if v.pref.is_test {
|
|
||||||
v.module_lookup_paths << os.base_dir(v.compiled_dir) // pdir of _test.v
|
|
||||||
}
|
|
||||||
v.module_lookup_paths << v.compiled_dir
|
|
||||||
x := os.join_path(v.compiled_dir, 'modules')
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
println('x: "$x"')
|
|
||||||
}
|
|
||||||
v.module_lookup_paths << os.join_path(v.compiled_dir, 'modules')
|
|
||||||
v.module_lookup_paths << v.pref.lookup_path
|
|
||||||
if v.pref.verbosity.is_higher_or_equal(.level_two) {
|
|
||||||
v.log('v.module_lookup_paths') //: $v.module_lookup_paths')
|
|
||||||
println(v.module_lookup_paths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) find_module_path(mod string) ?string {
|
|
||||||
vmod_file_location := p.v.mod_file_cacher.get( p.file_path_dir )
|
|
||||||
mut module_lookup_paths := []string
|
|
||||||
if vmod_file_location.vmod_file.len != 0 {
|
|
||||||
if ! vmod_file_location.vmod_folder in p.v.module_lookup_paths {
|
|
||||||
module_lookup_paths << vmod_file_location.vmod_folder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module_lookup_paths << p.v.module_lookup_paths
|
|
||||||
|
|
||||||
mod_path := p.v.module_path(mod)
|
|
||||||
for lookup_path in module_lookup_paths {
|
|
||||||
try_path := os.join_path(lookup_path, mod_path)
|
|
||||||
if p.v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println(' >> trying to find $mod in $try_path ...')
|
|
||||||
}
|
|
||||||
if os.is_dir(try_path) {
|
|
||||||
if p.v.pref.verbosity.is_higher_or_equal(.level_three) {
|
|
||||||
println(' << found $try_path .')
|
|
||||||
}
|
|
||||||
return try_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return error('module "$mod" not found in ${module_lookup_paths}')
|
|
||||||
}
|
|
||||||
|
|
||||||
[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_', '.')
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
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)
|
|
||||||
if p.tok == .rsbr {
|
|
||||||
p.error('`x in []` is always false')
|
|
||||||
}
|
|
||||||
mut i := 0
|
|
||||||
// Get `a` expr value (can be a string literal, not a variable)
|
|
||||||
expr := p.cgen.cur_line[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.cgen.set_placeholder(ph, ' (')
|
|
||||||
p.gen(' ==')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.check_types(p.bool_expression(), typ)
|
|
||||||
if is_str {
|
|
||||||
p.gen(')')
|
|
||||||
}
|
|
||||||
if p.tok != .rsbr {
|
|
||||||
p.check(.comma)
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
p.gen(')')
|
|
||||||
p.check(.rsbr)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,340 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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 = []
|
|
||||||
if false {
|
|
||||||
}
|
|
||||||
p.sql_types = []
|
|
||||||
mut q := 'select '
|
|
||||||
p.check(.key_select)
|
|
||||||
p.fspace()
|
|
||||||
n := p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
if n == 'count' {
|
|
||||||
q += 'count(*) from '
|
|
||||||
p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
table_name := p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
// 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 in ['string', 'int', 'bool']) {
|
|
||||||
println('orm: skipping $field.name')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if field.attr.contains('skip') {
|
|
||||||
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 in ['string', 'int', 'bool']) {
|
|
||||||
println('orm: skipping $field.name')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.register_var({
|
|
||||||
field |
|
|
||||||
is_mut:true,
|
|
||||||
is_used:true,
|
|
||||||
is_changed:true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
q += table_name + 's'
|
|
||||||
// `where` statement
|
|
||||||
if p.tok == .name && p.lit == 'where' {
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
p.is_sql = true
|
|
||||||
_,expr := p.tmp_expr()
|
|
||||||
p.is_sql = false
|
|
||||||
q += ' where ' + expr
|
|
||||||
}
|
|
||||||
// limit?
|
|
||||||
mut query_one := false
|
|
||||||
if p.tok == .name && p.lit == 'limit' {
|
|
||||||
p.fspace()
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
p.is_sql = true
|
|
||||||
_,limit := p.tmp_expr()
|
|
||||||
p.fspace()
|
|
||||||
p.is_sql = false
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
else if field.typ == 'bool' {
|
|
||||||
cast = 'string_bool'
|
|
||||||
}
|
|
||||||
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_builtin(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)')
|
|
||||||
}
|
|
||||||
|
|
||||||
// `db.update User set nr_orders=nr_orders+1`
|
|
||||||
fn (p mut Parser) update_query(fn_ph int) {
|
|
||||||
println('update query')
|
|
||||||
p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
table_name := p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
typ := p.table.find_type(table_name)
|
|
||||||
if typ.name == '' {
|
|
||||||
p.error('unknown type `$table_name`')
|
|
||||||
}
|
|
||||||
set := p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
if set != 'set' {
|
|
||||||
p.error('expected `set`')
|
|
||||||
}
|
|
||||||
if typ.fields.len == 0 {
|
|
||||||
p.error('V orm: update: empty fields in `$typ.name`')
|
|
||||||
}
|
|
||||||
if typ.fields[0].name != 'id' {
|
|
||||||
p.error('V orm: `id int` must be the first field in `$typ.name`')
|
|
||||||
}
|
|
||||||
field := p.check_name()
|
|
||||||
p.fspace()
|
|
||||||
p.check(.assign)
|
|
||||||
p.fspace()
|
|
||||||
for f in typ.fields {
|
|
||||||
if !(f.typ in ['string', 'int', 'bool']) {
|
|
||||||
println('orm: skipping $f.name')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.register_var({
|
|
||||||
f |
|
|
||||||
is_mut:true,
|
|
||||||
is_used:true,
|
|
||||||
is_changed:true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
mut q := 'update ${typ.name}s set $field='
|
|
||||||
p.is_sql = true
|
|
||||||
set_typ,expr := p.tmp_expr()
|
|
||||||
p.is_sql = false
|
|
||||||
// TODO this hack should not be necessary
|
|
||||||
if set_typ == 'bool' {
|
|
||||||
if expr.trim_space() == '1' {
|
|
||||||
q += 'true'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
q += 'false'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
q += expr
|
|
||||||
}
|
|
||||||
// where
|
|
||||||
if p.tok == .name && p.lit == 'where' {
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
p.is_sql = true
|
|
||||||
_,wexpr := p.tmp_expr()
|
|
||||||
p.is_sql = false
|
|
||||||
q += ' where ' + wexpr
|
|
||||||
}
|
|
||||||
nr_vals := 0
|
|
||||||
p.cgen.insert_before('char* params[$nr_vals];') // + params)
|
|
||||||
p.cgen.set_placeholder(fn_ph, 'PQexecParams( ')
|
|
||||||
println('update q="$q"')
|
|
||||||
p.genln('.conn, "$q", $nr_vals, 0, params, 0, 0, 0)')
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,162 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
fn (p mut Parser) string_expr() {
|
|
||||||
is_raw := p.tok == .name && p.lit == 'r'
|
|
||||||
is_cstr := p.tok == .name && p.lit == 'c'
|
|
||||||
if is_raw || is_cstr {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
str := p.lit
|
|
||||||
// No ${}, just return a simple string
|
|
||||||
if p.peek() != .str_dollar || is_raw {
|
|
||||||
f := if is_raw { cescaped_path(str).replace('"', '\\"') } else { format_str(str) }
|
|
||||||
// `C.puts('hi')` => `puts("hi");`
|
|
||||||
/*
|
|
||||||
Calling a C function sometimes requires a call to a string method
|
|
||||||
C.fun('ssss'.to_wide()) => fun(string_to_wide(tos3("ssss")))
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (p.calling_c && p.peek() != .dot) || is_cstr || (p.pref.translated && p.mod == 'main') {
|
|
||||||
if p.os == .windows && p.mod == 'ui' {
|
|
||||||
p.gen('L"$f"')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('"$f"')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if p.is_sql {
|
|
||||||
p.gen("'$str'")
|
|
||||||
}
|
|
||||||
else if p.is_js {
|
|
||||||
p.gen('tos("$f")')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.gen('tos3("$f")')
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
if p.scanner.is_fmt && p.tok == .not {
|
|
||||||
// handle '$age'!
|
|
||||||
// TODO remove this hack, do this automatically
|
|
||||||
p.fgen(' ')
|
|
||||||
p.check(.not)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$if js {
|
|
||||||
p.error('js backend does not support string formatting yet')
|
|
||||||
}
|
|
||||||
p.is_alloc = true // $ interpolation means there's allocation
|
|
||||||
mut args := '"'
|
|
||||||
mut format := '"'
|
|
||||||
mut complex_inter := false // for vfmt
|
|
||||||
for p.tok == .string{
|
|
||||||
// Add the string between %d's
|
|
||||||
p.lit = p.lit.replace('%', '%%')
|
|
||||||
format += format_str(p.lit)
|
|
||||||
p.next() // skip $
|
|
||||||
if p.tok != .str_dollar {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Handle .dollar
|
|
||||||
p.check(.str_dollar)
|
|
||||||
// If there's no string after current token, it means we are in
|
|
||||||
// a complex expression (`${...}`)
|
|
||||||
if p.peek() != .string{
|
|
||||||
p.fgen('{')
|
|
||||||
complex_inter = true
|
|
||||||
}
|
|
||||||
// Get bool expr inside a temp var
|
|
||||||
typ,val_ := p.tmp_expr()
|
|
||||||
val := val_.trim_space()
|
|
||||||
args += ', $val'
|
|
||||||
if typ == 'string' {
|
|
||||||
// args += '.str'
|
|
||||||
// printf("%.*s", a.len, a.str) syntax
|
|
||||||
args += '.len, ${val}.str'
|
|
||||||
}
|
|
||||||
if typ == 'ustring' {
|
|
||||||
args += '.len, ${val}.s.str'
|
|
||||||
}
|
|
||||||
if typ == 'bool' {
|
|
||||||
// args += '.len, ${val}.str'
|
|
||||||
}
|
|
||||||
// Custom format? ${t.hour:02d}
|
|
||||||
custom := p.tok == .colon
|
|
||||||
if custom {
|
|
||||||
mut cformat := ''
|
|
||||||
p.next()
|
|
||||||
if p.tok == .dot {
|
|
||||||
cformat += '.'
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
if p.tok == .minus {
|
|
||||||
// support for left aligned formatting
|
|
||||||
cformat += '-'
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
cformat += p.lit // 02
|
|
||||||
p.next()
|
|
||||||
fspec := p.lit // f
|
|
||||||
cformat += fspec
|
|
||||||
if fspec == 's' {
|
|
||||||
// println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ')
|
|
||||||
if typ != 'string' {
|
|
||||||
p.error('only V strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}')
|
|
||||||
}
|
|
||||||
args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str'
|
|
||||||
}
|
|
||||||
format += '%$cformat'
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
f := p.typ_to_fmt(typ, 0)
|
|
||||||
if f == '' {
|
|
||||||
has_str_method, styp := p.gen_default_str_method_if_missing( typ )
|
|
||||||
if has_str_method {
|
|
||||||
tmp_var := p.get_tmp()
|
|
||||||
p.cgen.insert_before('string $tmp_var = ${styp}_str(${val});')
|
|
||||||
args = args.all_before_last(val) + '${tmp_var}.len, ${tmp_var}.str'
|
|
||||||
format += '%.*s '
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.error('unhandled sprintf format "$typ" ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format += f
|
|
||||||
}
|
|
||||||
// println('interpolation format is: |${format}| args are: |${args}| ')
|
|
||||||
}
|
|
||||||
if complex_inter {
|
|
||||||
p.fgen('}')
|
|
||||||
}
|
|
||||||
|
|
||||||
// p.fgen('\'')
|
|
||||||
// println("hello %d", num) optimization.
|
|
||||||
if p.cgen.nogen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// println: don't allocate a new string, just print it.
|
|
||||||
$if !windows {
|
|
||||||
cur_line := p.cgen.cur_line.trim_space()
|
|
||||||
if cur_line == 'println (' && p.tok != .plus {
|
|
||||||
p.cgen.resetln(cur_line.replace('println (', 'printf('))
|
|
||||||
p.gen('$format\\n$args')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// '$age'! means the user wants this to be a tmp string (uses global buffer, no allocation,
|
|
||||||
// won't be used again)
|
|
||||||
// TODO remove this hack, do this automatically
|
|
||||||
if p.tok == .not {
|
|
||||||
p.fgen(' ')
|
|
||||||
p.check(.not)
|
|
||||||
p.gen('_STR_TMP($format$args)')
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Otherwise do len counting + allocation + sprintf
|
|
||||||
p.gen('_STR($format$args)')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,536 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
)
|
|
||||||
// also unions and interfaces
|
|
||||||
|
|
||||||
|
|
||||||
fn (p mut Parser) struct_decl(generic_param_types []string) {
|
|
||||||
decl_tok_idx := p.cur_tok_index()
|
|
||||||
is_pub := p.tok == .key_pub
|
|
||||||
if is_pub {
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
}
|
|
||||||
// V can generate Objective C for integration with Cocoa
|
|
||||||
// `[objc_interface:ParentInterface]`
|
|
||||||
is_objc := p.attr.starts_with('objc_interface')
|
|
||||||
objc_parent := if is_objc { p.attr[15..] } else { '' }
|
|
||||||
// interface, union, struct
|
|
||||||
is_interface := p.tok == .key_interface
|
|
||||||
is_union := p.tok == .key_union
|
|
||||||
is_struct := p.tok == .key_struct
|
|
||||||
mut cat := key_to_type_cat(p.tok)
|
|
||||||
if is_objc {
|
|
||||||
cat = .objc_interface
|
|
||||||
}
|
|
||||||
p.next()
|
|
||||||
p.fspace()
|
|
||||||
// Get type name
|
|
||||||
mut name := p.check_name()
|
|
||||||
if name.contains('_') && !p.pref.translated {
|
|
||||||
p.error('type names cannot contain `_`')
|
|
||||||
}
|
|
||||||
if !p.builtin_mod && !name[0].is_capital() {
|
|
||||||
p.error('mod=$p.mod struct names must be capitalized: use `struct ${name.capitalize()}`')
|
|
||||||
}
|
|
||||||
if is_interface && !name.ends_with('er') && name[0] != `I` {
|
|
||||||
p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)')
|
|
||||||
}
|
|
||||||
mut generic_types := map[string]string
|
|
||||||
mut is_generic := false
|
|
||||||
if p.tok == .lt {
|
|
||||||
p.check(.lt)
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
if generic_param_types.len > 0 && i > generic_param_types.len - 1 {
|
|
||||||
p.error('mismatched generic type params')
|
|
||||||
}
|
|
||||||
type_param := p.check_name()
|
|
||||||
generic_types[type_param] = if generic_param_types.len > 0 { generic_param_types[i] } else { '' }
|
|
||||||
if p.tok != .comma {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.check(.comma)
|
|
||||||
}
|
|
||||||
p.check(.gt)
|
|
||||||
is_generic = true
|
|
||||||
}
|
|
||||||
is_generic_instance := is_generic && generic_param_types.len > 0
|
|
||||||
is_c := name == 'C' && p.tok == .dot
|
|
||||||
if is_c {
|
|
||||||
/*
|
|
||||||
if !p.pref.building_v && !p.fileis('vlib') {
|
|
||||||
p.warn('Virtual C structs will soon be removed from the language' +
|
|
||||||
'\ndefine the C structs and functions in V')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
p.check(.dot)
|
|
||||||
name = p.check_name()
|
|
||||||
cat = .c_struct
|
|
||||||
if p.attr == 'typedef' {
|
|
||||||
cat = .c_typedef
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if name.len == 1 && !p.pref.building_v && !p.pref.is_repl {
|
|
||||||
p.warn('struct names must have more than one character ("$name", len=$name.len, $p.pref.building_v)')
|
|
||||||
}
|
|
||||||
if !is_c && !good_type_name(name) {
|
|
||||||
p.error('bad struct name, e.g. use `HttpRequest` instead of `HTTPRequest`')
|
|
||||||
}
|
|
||||||
// Specify full type name
|
|
||||||
if !is_c && !p.builtin_mod && p.mod != 'main' {
|
|
||||||
name = p.prepend_mod(name)
|
|
||||||
}
|
|
||||||
mut typ := p.table.find_type(name)
|
|
||||||
if p.pass == .decl && p.table.known_type_fast(typ) {
|
|
||||||
// if name in reserved_type_param_names {
|
|
||||||
// p.error('name `$name` is reserved for type parameters')
|
|
||||||
// } else {
|
|
||||||
p.error('type `$name` redeclared')
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
if is_objc {
|
|
||||||
// Forward declaration of an Objective-C interface with `@class` :)
|
|
||||||
p.gen_typedef('@class $name;')
|
|
||||||
}
|
|
||||||
else if !is_c {
|
|
||||||
kind := if is_union { 'union' } else { 'struct' }
|
|
||||||
p.gen_typedef('typedef $kind $name $name;')
|
|
||||||
}
|
|
||||||
// TODO: handle error
|
|
||||||
parser_idx := p.v.get_file_parser_index(p.file_path) or {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
// if !p.scanner.is_vh {
|
|
||||||
// parser_idx = p.v.get_file_parser_index(p.file_path) or { panic('cant find parser idx for $p.file_path') }
|
|
||||||
// }
|
|
||||||
// Register the type
|
|
||||||
mut is_ph := false
|
|
||||||
if typ.is_placeholder {
|
|
||||||
// Update the placeholder
|
|
||||||
is_ph = true
|
|
||||||
typ.name = name
|
|
||||||
typ.mod = p.mod
|
|
||||||
typ.is_c = is_c
|
|
||||||
typ.is_placeholder = false
|
|
||||||
typ.cat = cat
|
|
||||||
typ.parent = objc_parent
|
|
||||||
typ.is_public = is_pub || p.is_vh
|
|
||||||
typ.is_generic = is_generic && !is_generic_instance
|
|
||||||
typ.decl_tok_idx = decl_tok_idx
|
|
||||||
typ.parser_idx = parser_idx
|
|
||||||
p.table.rewrite_type(typ)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
typ = Type{
|
|
||||||
name: name
|
|
||||||
mod: p.mod
|
|
||||||
is_c: is_c
|
|
||||||
cat: cat
|
|
||||||
parent: objc_parent
|
|
||||||
is_public: is_pub || p.is_vh
|
|
||||||
is_generic: is_generic && !is_generic_instance
|
|
||||||
decl_tok_idx: decl_tok_idx
|
|
||||||
parser_idx: parser_idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Struct `C.Foo` declaration, no body
|
|
||||||
if is_c && is_struct && p.tok != .lcbr {
|
|
||||||
p.table.register_type(typ)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// generic struct
|
|
||||||
if is_generic {
|
|
||||||
// template
|
|
||||||
if !is_generic_instance {
|
|
||||||
p.table.register_type(typ)
|
|
||||||
p.table.generic_struct_params[typ.name] = generic_types.keys()
|
|
||||||
// NOTE: remove to store fields in generic struct template
|
|
||||||
p.skip_block(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// instance
|
|
||||||
else {
|
|
||||||
typ.rename_generic_struct(generic_types)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
p.check(.lcbr)
|
|
||||||
// Struct fields
|
|
||||||
mut access_mod := AccessMod.private
|
|
||||||
// mut is_pub_field := false
|
|
||||||
// mut is_mut := false
|
|
||||||
mut names := []string // to avoid dup names TODO alloc perf
|
|
||||||
mut fmt_max_len := p.table.max_field_len[name]
|
|
||||||
// println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass')
|
|
||||||
if (!is_ph && p.first_pass()) || is_generic {
|
|
||||||
p.table.register_type(typ)
|
|
||||||
// println('registering 1 nrfields=$typ.fields.len')
|
|
||||||
}
|
|
||||||
mut did_gen_something := false
|
|
||||||
mut used := []AccessMod
|
|
||||||
mut i := -1
|
|
||||||
for p.tok != .rcbr {
|
|
||||||
i++
|
|
||||||
mut new_access_mod := access_mod
|
|
||||||
if p.tok == .key_pub {
|
|
||||||
p.fmt_dec()
|
|
||||||
p.check(.key_pub)
|
|
||||||
if p.tok == .key_mut {
|
|
||||||
p.fspace()
|
|
||||||
new_access_mod = .public_mut
|
|
||||||
p.next() // skip `mut`
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
new_access_mod = .public
|
|
||||||
}
|
|
||||||
if new_access_mod in used {
|
|
||||||
p.error('structs can only have one `pub:`/`pub mut:`, all public fields have to be grouped')
|
|
||||||
}
|
|
||||||
p.check(.colon)
|
|
||||||
p.fmt_inc()
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
else if p.tok == .key_mut {
|
|
||||||
new_access_mod = .private_mut
|
|
||||||
if new_access_mod in used {
|
|
||||||
p.error('structs can only have one `mut:`, all private mutable fields have to be grouped')
|
|
||||||
}
|
|
||||||
p.fmt_dec()
|
|
||||||
p.check(.key_mut)
|
|
||||||
p.check(.colon)
|
|
||||||
p.fmt_inc()
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
else if p.tok == .key_global {
|
|
||||||
new_access_mod = .global
|
|
||||||
if new_access_mod in used {
|
|
||||||
p.error('structs can only have one `__global:`, all global fields have to be grouped')
|
|
||||||
}
|
|
||||||
p.fmt_dec()
|
|
||||||
p.check(.key_global)
|
|
||||||
p.check(.colon)
|
|
||||||
p.fmt_inc()
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
if new_access_mod != access_mod {
|
|
||||||
used << new_access_mod
|
|
||||||
}
|
|
||||||
access_mod = new_access_mod
|
|
||||||
// (mut) user *User
|
|
||||||
// if p.tok == .plus {
|
|
||||||
// p.next()
|
|
||||||
// }
|
|
||||||
// Check if reserved name
|
|
||||||
field_name_token_idx := p.cur_tok_index()
|
|
||||||
field_name := if name != 'Option' && !is_interface { p.table.var_cgen_name(p.check_name()) } else { p.check_name() }
|
|
||||||
if p.pass == .main {
|
|
||||||
p.fgen(strings.repeat(` `, fmt_max_len - field_name.len))
|
|
||||||
}
|
|
||||||
// Check dups
|
|
||||||
if field_name in names {
|
|
||||||
p.error('duplicate field `$field_name`')
|
|
||||||
}
|
|
||||||
if p.scanner.is_fmt && p.pass == .decl && field_name.len > fmt_max_len {
|
|
||||||
fmt_max_len = field_name.len
|
|
||||||
}
|
|
||||||
if !is_c && p.mod != 'os' && contains_capital(field_name) {
|
|
||||||
p.error('struct fields cannot contain uppercase letters, use snake_case instead')
|
|
||||||
}
|
|
||||||
names << field_name
|
|
||||||
// We are in an interface?
|
|
||||||
// `run() string` => run is a method, not a struct field
|
|
||||||
if is_interface {
|
|
||||||
f := p.interface_method(field_name, name)
|
|
||||||
if p.first_pass() {
|
|
||||||
p.add_method(typ.name, f)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// `pub` access mod
|
|
||||||
// access_mod := if is_pub_field { AccessMod.public } else { AccessMod.private}
|
|
||||||
p.fspace()
|
|
||||||
defer {
|
|
||||||
if is_generic_instance {
|
|
||||||
p.generic_dispatch = TypeInst{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is_generic_instance {
|
|
||||||
p.generic_dispatch = TypeInst{
|
|
||||||
inst: generic_types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tt := p.get_type2()
|
|
||||||
field_type := tt.name
|
|
||||||
if field_type == name {
|
|
||||||
p.error_with_token_index('cannot embed struct `$name` in itself (field `$field_name`)', field_name_token_idx)
|
|
||||||
}
|
|
||||||
// Register ?option type
|
|
||||||
if field_type.starts_with('Option_') {
|
|
||||||
p.gen_typedef('typedef Option $field_type;')
|
|
||||||
}
|
|
||||||
p.check_and_register_used_imported_type(field_type)
|
|
||||||
is_atomic := p.tok == .key_atomic
|
|
||||||
if is_atomic {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
// `a int = 4`
|
|
||||||
if p.tok == .assign {
|
|
||||||
p.next()
|
|
||||||
def_val_type,expr := p.tmp_expr()
|
|
||||||
if def_val_type != field_type {
|
|
||||||
p.error('expected `$field_type` but got `$def_val_type`')
|
|
||||||
}
|
|
||||||
// println('pass=$p.pass $typ.name ADDING field=$field_name "$def_val_type" "$expr"')
|
|
||||||
if !p.first_pass() {
|
|
||||||
p.table.add_default_val(i, typ.name, expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// [ATTR]
|
|
||||||
mut attr := ''
|
|
||||||
if p.tok == .lsbr {
|
|
||||||
p.fspace()
|
|
||||||
p.next()
|
|
||||||
attr = p.check_name()
|
|
||||||
if p.tok == .colon {
|
|
||||||
p.check(.colon)
|
|
||||||
mut val := ''
|
|
||||||
match p.tok {
|
|
||||||
.name {
|
|
||||||
val = p.check_name()
|
|
||||||
}
|
|
||||||
.string{
|
|
||||||
val = p.check_string()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
p.error('attribute value should be either name or string')
|
|
||||||
}}
|
|
||||||
attr += ':' + val
|
|
||||||
}
|
|
||||||
p.check(.rsbr)
|
|
||||||
}
|
|
||||||
if attr == 'raw' && field_type != 'string' {
|
|
||||||
p.error('struct field with attribute "raw" should be of type "string" but got "$field_type"')
|
|
||||||
}
|
|
||||||
did_gen_something = true
|
|
||||||
is_mut := access_mod in [.private_mut, .public_mut, .global]
|
|
||||||
if p.first_pass() || is_generic {
|
|
||||||
p.table.add_field(typ.name, field_name, field_type, is_mut, attr, access_mod)
|
|
||||||
}
|
|
||||||
p.fgen_nl() // newline between struct fields
|
|
||||||
}
|
|
||||||
if p.scanner.is_fmt && p.pass == .decl {
|
|
||||||
p.table.max_field_len[typ.name] = fmt_max_len
|
|
||||||
}
|
|
||||||
// p.fgen_require_nl()
|
|
||||||
p.check(.rcbr)
|
|
||||||
if !is_c && !did_gen_something && p.first_pass() {
|
|
||||||
p.table.add_field(typ.name, '', 'EMPTY_STRUCT_DECLARATION', false, '', .private)
|
|
||||||
}
|
|
||||||
p.fgen_nl()
|
|
||||||
p.fgen_nl()
|
|
||||||
// p.fgenln('//kek')
|
|
||||||
}
|
|
||||||
// `User{ foo: bar }`
|
|
||||||
// tok == struct name
|
|
||||||
fn (p mut Parser) struct_init(typ_ string) string {
|
|
||||||
p.is_struct_init = true
|
|
||||||
mut typ := typ_
|
|
||||||
mut t := p.table.find_type(typ)
|
|
||||||
if !t.is_public && t.mod != p.mod {
|
|
||||||
p.error('struct `$t.name` is private')
|
|
||||||
}
|
|
||||||
// generic struct init
|
|
||||||
if p.peek() == .lt {
|
|
||||||
p.next()
|
|
||||||
p.check(.lt)
|
|
||||||
mut type_params := []string
|
|
||||||
for {
|
|
||||||
mut type_param := p.check_name()
|
|
||||||
if type_param in p.generic_dispatch.inst {
|
|
||||||
type_param = p.generic_dispatch.inst[type_param]
|
|
||||||
}
|
|
||||||
type_params << type_param
|
|
||||||
if p.tok != .comma {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.check(.comma)
|
|
||||||
}
|
|
||||||
p.dispatch_generic_struct(mut t, type_params)
|
|
||||||
t = p.table.find_type(t.name)
|
|
||||||
typ = t.name
|
|
||||||
}
|
|
||||||
if p.gen_struct_init(typ, t) {
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
ptr := typ.contains('*')
|
|
||||||
mut did_gen_something := false
|
|
||||||
// Loop thru all struct init keys and assign values
|
|
||||||
// u := User{age:20, name:'bob'}
|
|
||||||
// Remember which fields were set, so that we dont have to zero them later
|
|
||||||
mut inited_fields := []string
|
|
||||||
peek := p.peek()
|
|
||||||
if peek == .colon || p.tok == .rcbr {
|
|
||||||
for p.tok != .rcbr {
|
|
||||||
field := if typ != 'Option' { p.table.var_cgen_name(p.check_name()) } else { p.check_name() }
|
|
||||||
if !p.first_pass() && !t.has_field(field) {
|
|
||||||
p.error('`$t.name` has no field `$field`')
|
|
||||||
}
|
|
||||||
if field in inited_fields {
|
|
||||||
p.error('already initialized field `$field` in `$t.name`')
|
|
||||||
}
|
|
||||||
f := t.find_field(field) or {
|
|
||||||
p.error('no such field: "$field" in type $typ')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
tt := p.table.find_type(f.typ)
|
|
||||||
if tt.is_flag {
|
|
||||||
p.error(err_modify_bitfield)
|
|
||||||
}
|
|
||||||
inited_fields << field
|
|
||||||
p.gen_struct_field_init(field)
|
|
||||||
p.check(.colon)
|
|
||||||
p.fspace()
|
|
||||||
p.expected_type = f.typ
|
|
||||||
p.check_types(p.bool_expression(), f.typ)
|
|
||||||
if p.tok == .comma {
|
|
||||||
p.next()
|
|
||||||
p.fremove_last()
|
|
||||||
}
|
|
||||||
if p.tok != .rcbr {
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
p.fspace()
|
|
||||||
did_gen_something = true
|
|
||||||
p.fgen_nl() // newline between struct fields
|
|
||||||
}
|
|
||||||
// If we already set some fields, need to prepend a comma
|
|
||||||
if t.fields.len != inited_fields.len && inited_fields.len > 0 {
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
// Zero values: init all fields (ints to 0, strings to '' etc)
|
|
||||||
for i, field in t.fields {
|
|
||||||
sanitized_name := if typ != 'Option' { p.table.var_cgen_name(field.name) } else { field.name }
|
|
||||||
// println('### field.name')
|
|
||||||
// Skip if this field has already been assigned to
|
|
||||||
if sanitized_name in inited_fields {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
field_typ := field.typ
|
|
||||||
if !p.builtin_mod && field_typ.ends_with('*') && !p.is_c_struct_init && p.mod != 'os' &&
|
|
||||||
p.mod != 'ui' {
|
|
||||||
p.warn('reference field `${typ}.${field.name}` must be initialized')
|
|
||||||
}
|
|
||||||
// init map fields
|
|
||||||
if field_typ.starts_with('map_') {
|
|
||||||
p.gen_struct_field_init(sanitized_name)
|
|
||||||
p.gen_empty_map(parse_pointer(field_typ[4..]))
|
|
||||||
inited_fields << sanitized_name
|
|
||||||
if i != t.fields.len - 1 {
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
did_gen_something = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Did the user provide a default value for this struct field?
|
|
||||||
// Use it. Otherwise zero it.
|
|
||||||
def_val := if t.default_vals.len > i && t.default_vals[i] != '' { t.default_vals[i] } else { type_default(field_typ) }
|
|
||||||
if def_val != '' && def_val != '{0}' {
|
|
||||||
p.gen_struct_field_init(sanitized_name)
|
|
||||||
p.gen(def_val)
|
|
||||||
if i != t.fields.len - 1 {
|
|
||||||
p.gen(',')
|
|
||||||
}
|
|
||||||
did_gen_something = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Point{3,4} syntax
|
|
||||||
else {
|
|
||||||
mut T := p.table.find_type(typ)
|
|
||||||
// Aliases (TODO Hack, implement proper aliases)
|
|
||||||
if T.fields.len == 0 && T.parent != '' {
|
|
||||||
T = p.table.find_type(T.parent)
|
|
||||||
}
|
|
||||||
for i, ffield in T.fields {
|
|
||||||
expr_typ := p.bool_expression()
|
|
||||||
if !p.check_types_no_throw(expr_typ, ffield.typ) {
|
|
||||||
p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ')
|
|
||||||
}
|
|
||||||
tt := p.table.find_type(ffield.typ)
|
|
||||||
if tt.is_flag {
|
|
||||||
p.error(err_modify_bitfield)
|
|
||||||
}
|
|
||||||
if i < T.fields.len - 1 {
|
|
||||||
if p.tok != .comma {
|
|
||||||
p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)')
|
|
||||||
}
|
|
||||||
p.gen(',')
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Allow `user := User{1,2,3,}`
|
|
||||||
// The final comma will be removed by vfmt, since we are not calling `p.fgen()`
|
|
||||||
if p.tok == .comma {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
if p.tok != .rcbr {
|
|
||||||
p.error('too many fields initialized: `$typ` has $T.fields.len field(s)')
|
|
||||||
}
|
|
||||||
did_gen_something = true
|
|
||||||
}
|
|
||||||
if !did_gen_something {
|
|
||||||
p.gen('EMPTY_STRUCT_INITIALIZATION')
|
|
||||||
}
|
|
||||||
p.gen('}')
|
|
||||||
if ptr && !p.is_js {
|
|
||||||
p.gen(', sizeof($t.name))')
|
|
||||||
}
|
|
||||||
p.check(.rcbr)
|
|
||||||
p.is_struct_init = false
|
|
||||||
p.is_c_struct_init = false
|
|
||||||
return typ
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (t mut Type) rename_generic_struct(generic_types map[string]string) {
|
|
||||||
t.name = t.name + '_T'
|
|
||||||
for _, v in generic_types {
|
|
||||||
t.name = t.name + '_' + type_to_safe_str(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (p mut Parser) dispatch_generic_struct(t mut Type, type_params []string) {
|
|
||||||
mut generic_types := map[string]string
|
|
||||||
if t.name in p.table.generic_struct_params {
|
|
||||||
mut i := 0
|
|
||||||
for _, v in p.table.generic_struct_params[t.name] {
|
|
||||||
generic_types[v] = type_params[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
t.rename_generic_struct(generic_types)
|
|
||||||
if p.table.known_type(t.name) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.cgen.typedefs << 'typedef struct $t.name $t.name;\n'
|
|
||||||
}
|
|
||||||
mut gp := p.v.parsers[t.parser_idx]
|
|
||||||
gp.is_vgen = true
|
|
||||||
saved_state := p.save_state()
|
|
||||||
p.clear_state(false, true)
|
|
||||||
gp.token_idx = t.decl_tok_idx
|
|
||||||
// FIXME: TODO: why are tokens cleared?
|
|
||||||
if gp.tokens.len == 0 {
|
|
||||||
gp.scanner.pos = 0
|
|
||||||
gp.scan_tokens()
|
|
||||||
}
|
|
||||||
gp.next()
|
|
||||||
gp.struct_decl(type_params)
|
|
||||||
p.cgen.lines_extra << p.cgen.lines
|
|
||||||
p.restore_state(saved_state, false, true)
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,310 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
struct Token {
|
|
||||||
tok TokenKind // the token number/enum; for quick comparisons
|
|
||||||
lit string // literal representation of the token
|
|
||||||
line_nr int // the line number in the source where the token occured
|
|
||||||
name_idx int // name table index for O(1) lookup
|
|
||||||
pos int // the position of the token in scanner text
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TokenKind {
|
|
||||||
eof
|
|
||||||
name // user
|
|
||||||
number // 123
|
|
||||||
string // '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 // =>
|
|
||||||
left_arrow // <-
|
|
||||||
amp
|
|
||||||
hash
|
|
||||||
dollar
|
|
||||||
str_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_comment
|
|
||||||
mline_comment
|
|
||||||
nl
|
|
||||||
dot
|
|
||||||
dotdot
|
|
||||||
ellipsis
|
|
||||||
// keywords
|
|
||||||
keyword_beg
|
|
||||||
key_as
|
|
||||||
key_asm
|
|
||||||
key_assert
|
|
||||||
key_atomic
|
|
||||||
key_break
|
|
||||||
key_const
|
|
||||||
key_continue
|
|
||||||
key_defer
|
|
||||||
key_else
|
|
||||||
key_embed
|
|
||||||
key_enum
|
|
||||||
key_false
|
|
||||||
key_for
|
|
||||||
key_fn
|
|
||||||
key_global
|
|
||||||
key_go
|
|
||||||
key_goto
|
|
||||||
key_if
|
|
||||||
key_import
|
|
||||||
key_import_const
|
|
||||||
key_in
|
|
||||||
key_interface
|
|
||||||
// key_it
|
|
||||||
key_match
|
|
||||||
key_module
|
|
||||||
key_mut
|
|
||||||
key_none
|
|
||||||
key_return
|
|
||||||
key_select
|
|
||||||
key_sizeof
|
|
||||||
key_offsetof
|
|
||||||
key_nameof
|
|
||||||
key_struct
|
|
||||||
key_switch
|
|
||||||
key_true
|
|
||||||
key_type
|
|
||||||
key_typeof
|
|
||||||
key_orelse
|
|
||||||
key_union
|
|
||||||
key_pub
|
|
||||||
key_static
|
|
||||||
key_unsafe
|
|
||||||
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] = 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.string] = '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_comment] = '// line comment'
|
|
||||||
s[TokenKind.mline_comment] = '/* mline comment */'
|
|
||||||
s[TokenKind.nl] = 'NLL'
|
|
||||||
s[TokenKind.dollar] = '$'
|
|
||||||
s[TokenKind.str_dollar] = '$2'
|
|
||||||
s[TokenKind.key_assert] = 'assert'
|
|
||||||
s[TokenKind.key_struct] = 'struct'
|
|
||||||
s[TokenKind.key_if] = 'if'
|
|
||||||
// s[TokenKind.key_it] = 'it'
|
|
||||||
s[TokenKind.key_else] = 'else'
|
|
||||||
s[TokenKind.key_asm] = 'asm'
|
|
||||||
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_fn] = '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'
|
|
||||||
s[TokenKind.key_unsafe] = 'unsafe'
|
|
||||||
s[TokenKind.key_typeof] = 'typeof'
|
|
||||||
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'
|
|
||||||
s[TokenKind.key_offsetof] = '__offsetof'
|
|
||||||
s[TokenKind.key_nameof] = 'nameof'
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
NrTokens = 141
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (t TokenKind) str() string {
|
|
||||||
return TokenStr[int(t)]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (t TokenKind) is_decl() bool {
|
|
||||||
return t in [.key_enum, .key_interface, .key_fn, .key_struct, .key_type, .key_const, .key_import_const, .key_pub, .eof]
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
AssignTokens = [TokenKind.assign, .plus_assign, .minus_assign, .mult_assign, .div_assign, .xor_assign, .mod_assign, .or_assign, .and_assign, .righ_shift_assign, .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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (t Token) str() string {
|
|
||||||
if t.tok == .number {
|
|
||||||
return t.lit
|
|
||||||
}
|
|
||||||
if t.tok == .chartoken {
|
|
||||||
return '`$t.lit`'
|
|
||||||
}
|
|
||||||
if t.tok == .string {
|
|
||||||
return "'$t.lit'"
|
|
||||||
}
|
|
||||||
if t.tok == .eof {
|
|
||||||
return '.EOF'
|
|
||||||
}
|
|
||||||
if t.tok < .plus {
|
|
||||||
return t.lit // string, number etc
|
|
||||||
}
|
|
||||||
return t.tok.str()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (t Token) detailed_str() string {
|
|
||||||
return 'Token{ .line:${t.line_nr:4d}, .pos:${t.pos:5d}, .tok: ${t.tok:3d} } = $t '
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,310 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
import os
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (scanner mut Scanner) fgen(s_ string) {
|
|
||||||
mut s := s_
|
|
||||||
if s != ' ' {
|
|
||||||
//s = s.trim_space()
|
|
||||||
}
|
|
||||||
if scanner.fmt_line_empty {
|
|
||||||
s = strings.repeat(`\t`, scanner.fmt_indent) + s.trim_left(' ')
|
|
||||||
}
|
|
||||||
scanner.fmt_lines << s
|
|
||||||
//scanner.fmt_out << s
|
|
||||||
//scanner.fmt_out.write(s)
|
|
||||||
scanner.fmt_line_empty = false
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (scanner mut Scanner) fgenln(s_ string) {
|
|
||||||
mut s := s_.trim_right(' ')
|
|
||||||
if scanner.fmt_line_empty && scanner.fmt_indent > 0 {
|
|
||||||
s = strings.repeat(`\t`, scanner.fmt_indent) + s
|
|
||||||
}
|
|
||||||
scanner.fmt_lines << s
|
|
||||||
//println('s="$s"')
|
|
||||||
//scanner.fmt_lines << '//!'
|
|
||||||
scanner.fmt_lines << '\n'
|
|
||||||
//scanner.fmt_out.writeln(s)
|
|
||||||
scanner.fmt_line_empty = true
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fgen(s string) {
|
|
||||||
if p.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.scanner.fgen(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fspace() {
|
|
||||||
if p.first_pass() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.fgen(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fspace_or_newline() {
|
|
||||||
if p.first_pass() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.token_idx >= 2 && p.tokens[p.token_idx-1].line_nr !=
|
|
||||||
p.tokens[p.token_idx-2].line_nr {
|
|
||||||
p.fgen_nl()
|
|
||||||
} else {
|
|
||||||
p.fgen(' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fgenln(s string) {
|
|
||||||
if p.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.scanner.fgenln(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fgen_nl() {
|
|
||||||
if p.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//println(p.tok)
|
|
||||||
// Don't insert a newline after a comment
|
|
||||||
/*
|
|
||||||
if p.token_idx>0 && p.tokens[p.token_idx-1].tok == .line_comment &&
|
|
||||||
p.tokens[p.token_idx].tok != .line_comment {
|
|
||||||
p.scanner.fgenln('notin')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
///if p.token_idx > 0 && p.token_idx < p.tokens.len &&
|
|
||||||
// Previous token is a comment, and NL has already been generated?
|
|
||||||
// Don't generate a second NL.
|
|
||||||
if p.scanner.fmt_lines.len > 0 && p.scanner.fmt_lines.last() == '\n' &&
|
|
||||||
p.token_idx > 2 &&
|
|
||||||
p.tokens[p.token_idx-2].tok == .line_comment
|
|
||||||
{
|
|
||||||
//if p.fileis('parser.v') {
|
|
||||||
//println(p.scanner.line_nr.str() + ' ' +p.tokens[p.token_idx-2].str())
|
|
||||||
//}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.scanner.fgen_nl()
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (scanner mut Scanner) fgen_nl() {
|
|
||||||
//scanner.fmt_lines << ' fgen_nl'
|
|
||||||
//scanner.fmt_lines << '//fgen_nl\n'
|
|
||||||
scanner.fmt_lines << '\n'
|
|
||||||
//scanner.fmt_out.writeln('')
|
|
||||||
scanner.fmt_line_empty = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fmt_inc() {
|
|
||||||
if p.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.scanner.fmt_indent++
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fmt_dec() {
|
|
||||||
if p.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.scanner.fmt_indent--
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (s mut Scanner) init_fmt() {
|
|
||||||
// Right now we can't do `$if vfmt {`, so I'm using
|
|
||||||
// a conditional function init_fmt to set this flag.
|
|
||||||
// This function will only be called if `-d vfmt` is passed.
|
|
||||||
s.is_fmt = true
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fnext() {
|
|
||||||
//if p.tok == .eof {
|
|
||||||
//println('eof ret')
|
|
||||||
//return
|
|
||||||
//}
|
|
||||||
if p.tok == .rcbr && !p.inside_if_expr && p.prev_tok != .lcbr {
|
|
||||||
p.fmt_dec()
|
|
||||||
}
|
|
||||||
s := p.strtok()
|
|
||||||
if p.tok != .eof {
|
|
||||||
p.fgen(s)
|
|
||||||
}
|
|
||||||
// vfmt: increase indentation on `{` unless it's `{}`
|
|
||||||
inc_indent := false
|
|
||||||
if p.tok == .lcbr && !p.inside_if_expr && p.peek() != .rcbr {
|
|
||||||
p.fgen_nl()
|
|
||||||
p.fmt_inc()
|
|
||||||
}
|
|
||||||
if p.token_idx >= p.tokens.len {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Skip comments and add them to vfmt output
|
|
||||||
if p.tokens[p.token_idx].tok in [.line_comment, .mline_comment] {
|
|
||||||
// Newline before the comment and after consts and closing }
|
|
||||||
if p.inside_const {
|
|
||||||
//p.fgen_nl()
|
|
||||||
//p.fgen_nl()
|
|
||||||
}
|
|
||||||
//is_rcbr := p.tok == .rcbr
|
|
||||||
for p.token_idx < p.tokens.len - 1 {
|
|
||||||
i := p.token_idx
|
|
||||||
tok := p.tokens[p.token_idx].tok
|
|
||||||
if tok != .line_comment && tok != .mline_comment {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
comment_token := p.tokens[i]
|
|
||||||
next := p.tokens[i+1]
|
|
||||||
comment_on_new_line := i == 0 ||
|
|
||||||
comment_token.line_nr > p.tokens[i-1].line_nr
|
|
||||||
//prev_token := p.tokens[p.token_idx - 1]
|
|
||||||
comment := comment_token.lit
|
|
||||||
// Newline before the comment, but not between two // comments,
|
|
||||||
// and not right after `{`, there's already a newline there
|
|
||||||
if i > 0 && ((p.tokens[i-1].tok != .line_comment &&
|
|
||||||
p.tokens[i-1].tok != .lcbr &&
|
|
||||||
comment_token.line_nr > p.tokens[i-1].line_nr) ||
|
|
||||||
p.tokens[i-1].tok == .hash) { // TODO not sure why this is needed, newline wasn't added after a hash
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
if i > 0 && p.tokens[i-1].tok == .rcbr && p.scanner.fmt_indent == 0 {
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
if tok == .line_comment {
|
|
||||||
if !comment_on_new_line { //prev_token.line_nr < comment_token.line_nr {
|
|
||||||
p.fgen(' ')
|
|
||||||
}
|
|
||||||
p.fgen('// ' + comment)
|
|
||||||
/*
|
|
||||||
if false && i > 0 {
|
|
||||||
p.fgen(
|
|
||||||
'pln=${p.tokens[i-1].line_nr} ${comment_token.str()} ' +
|
|
||||||
'line_nr=$comment_token.line_nr next=${next.str()} next_line_nr=$next.line_nr')
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// /**/ comment
|
|
||||||
p.fgen(comment)
|
|
||||||
}
|
|
||||||
//if next.tok == .line_comment && comment_token.line_nr < next.line_nr {
|
|
||||||
if comment_token.line_nr < next.line_nr {
|
|
||||||
//p.fgenln('nextcm')
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
p.token_idx++
|
|
||||||
}
|
|
||||||
|
|
||||||
if inc_indent {
|
|
||||||
p.fgen_nl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p mut Parser) fremove_last() {
|
|
||||||
if p.scanner.fmt_lines.len > 0 {
|
|
||||||
p.scanner.fmt_lines[p.scanner.fmt_lines.len-1] = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[if vfmt]
|
|
||||||
fn (p &Parser) gen_fmt() {
|
|
||||||
if p.pass != .main {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//println('gen fmt name=$p.file_name path=$p.file_path')
|
|
||||||
if p.file_name == '' {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
is_all := p.v.v_fmt_all
|
|
||||||
vfmt_file := p.v.v_fmt_file
|
|
||||||
if p.file_path != vfmt_file && !is_all {
|
|
||||||
// skip everything except the last file (given by the CLI argument)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//s := p.scanner.fmt_out.str().replace('\n\n\n', '\n').trim_space()
|
|
||||||
//s := p.scanner.fmt_out.str().trim_space()
|
|
||||||
//p.scanner.fgenln('// nice')
|
|
||||||
mut s := p.scanner.fmt_lines.join('')
|
|
||||||
/*.replace_each([
|
|
||||||
'\n\n\n\n', '\n\n',
|
|
||||||
' \n', '\n',
|
|
||||||
') or{', ') or {',
|
|
||||||
])
|
|
||||||
*/
|
|
||||||
//.replace('\n\n\n\n', '\n\n')
|
|
||||||
|
|
||||||
s = s.replace(' \n', '\n')
|
|
||||||
s = s.replace(') or {', ') or {')
|
|
||||||
s = s.replace(') or{', ') or {')
|
|
||||||
s = s.replace(')or{', ') or {')
|
|
||||||
s = s.replace('or{', 'or {')
|
|
||||||
s = s.replace('}}\n', '}\n\t}\n')
|
|
||||||
|
|
||||||
if s == '' {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//files := ['get_type.v']
|
|
||||||
if p.file_path.contains('compiler/vfmt.v') {return}
|
|
||||||
//if !(p.file_name in files) { return }
|
|
||||||
if is_all {
|
|
||||||
if p.file_path.len > 0 {
|
|
||||||
path := write_formatted_source( p.file_name, s )
|
|
||||||
os.cp( path, p.file_path ) or { panic(err) }
|
|
||||||
eprintln('Written fmt file to: $p.file_path')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.file_path == vfmt_file {
|
|
||||||
res_path := write_formatted_source( p.file_name, s )
|
|
||||||
mut vv := p.v
|
|
||||||
vv.v_fmt_file_result = res_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_formatted_source(file_name string, s string) string {
|
|
||||||
path := os.temp_dir() + '/' + file_name
|
|
||||||
mut out := os.create(path) or {
|
|
||||||
verror('failed to create file $path')
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
//eprintln('replacing ${p.file_path} ...\n')
|
|
||||||
out.writeln(s.trim_space())//p.scanner.fmt_out.str().trim_space())
|
|
||||||
out.close()
|
|
||||||
return path
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright (c) 2019-2020 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
|
|
||||||
|
|
||||||
fn get_vtmp_folder() string {
|
|
||||||
vtmp := os.join_path(os.temp_dir(), 'v')
|
|
||||||
if !os.is_dir(vtmp) {
|
|
||||||
os.mkdir(vtmp) or {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vtmp
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_vtmp_filename(base_file_name string, postfix string) string {
|
|
||||||
vtmp := get_vtmp_folder()
|
|
||||||
return os.real_path(os.join_path(vtmp, os.file_name(os.real_path(base_file_name)) + postfix))
|
|
||||||
}
|
|
|
@ -384,6 +384,10 @@ fn (g mut Gen) stmt(node ast.Stmt) {
|
||||||
styp := g.typ(it.typ)
|
styp := g.typ(it.typ)
|
||||||
g.definitions.writeln('$styp $it.name; // global')
|
g.definitions.writeln('$styp $it.name; // global')
|
||||||
}
|
}
|
||||||
|
ast.GoStmt {
|
||||||
|
g.writeln('// go')
|
||||||
|
g.expr(it.expr)
|
||||||
|
}
|
||||||
ast.GotoLabel {
|
ast.GotoLabel {
|
||||||
g.writeln('$it.name:')
|
g.writeln('$it.name:')
|
||||||
}
|
}
|
||||||
|
@ -2534,6 +2538,19 @@ fn comp_if_to_ifdef(name string) string {
|
||||||
}
|
}
|
||||||
'no_bounds_checking' {
|
'no_bounds_checking' {
|
||||||
return 'NO_BOUNDS_CHECK'
|
return 'NO_BOUNDS_CHECK'
|
||||||
|
}
|
||||||
|
'x64' {
|
||||||
|
return 'TARGET_IS_64BIT'
|
||||||
|
}
|
||||||
|
'x32' {
|
||||||
|
return 'TARGET_IS_32BIT'
|
||||||
|
}
|
||||||
|
'little_endian' {
|
||||||
|
return 'TARGET_ORDER_IS_LITTLE'
|
||||||
|
|
||||||
|
}
|
||||||
|
'big_endian' {
|
||||||
|
return 'TARGET_ORDER_IS_BIG'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
verror('bad os ifdef name "$name"')
|
verror('bad os ifdef name "$name"')
|
||||||
|
|
Loading…
Reference in New Issue