remove 15k lines of code of the old backend; make V2 the default backend

pull/4182/head
Alexander Medvednikov 2020-04-01 21:24:58 +02:00
parent 30f306dc1a
commit 8dfb14b1c4
38 changed files with 462 additions and 15300 deletions

View File

@ -74,7 +74,9 @@ pub fn (ts mut TestSession) test() {
}
ts.files = remaining_files
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
})
pool_of_test_runners.set_shared_context(ts)

View File

@ -1,7 +1,7 @@
// 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
module compile
import (
os
@ -10,6 +10,15 @@ import (
term
)
pub const (
v_version = '0.1.26'
)
const (
v_modules_path = pref.default_module_path
)
fn todo() {
}
@ -219,6 +228,8 @@ fn (v mut V) cc() {
a << '-c'
}
else if v.pref.is_cache {
/*
QTODO
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
if os.exists(builtin_o_path) {
@ -260,7 +271,9 @@ fn (v mut V) cc() {
a << '-framework Cocoa -framework Carbon'
}
}
*/
}
if v.pref.sanitize {
a << '-fsanitize=leak'
}
@ -367,7 +380,7 @@ start:
==================
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()}
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() {
/*
QTODO
println('Cross compiling for Windows...')
if !c.pref.out_name.ends_with('.exe') {
c.pref.out_name += '.exe'
@ -533,6 +548,7 @@ fn (c mut V) cc_windows_cross() {
}
*/
println('Done!')
*/
}
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 {
$if 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 }
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)
}

View File

@ -1,7 +1,7 @@
// 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
module compile
import os
// parsed cflag
@ -24,6 +24,8 @@ fn (v &V) get_os_cflags() []CFlag {
ctimedefines << v.pref.compile_defines
}
// QTODO
/*
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) {
flags << flag
@ -32,6 +34,7 @@ fn (v &V) get_os_cflags() []CFlag {
flags << flag
}
}
*/
return flags
}
@ -63,6 +66,8 @@ fn (cf &CFlag) format() string {
}
// check if cflag is in table
/*
QTODO
fn (table &Table) has_cflag(cflag CFlag) bool {
for cf in table.cflags {
if cf.os == cflag.os && cf.name == cflag.name && cf.value == cflag.value {
@ -146,6 +151,7 @@ fn (table mut Table) parse_cflag(cflag string, mod string, ctimedefines []string
}
return true
}
*/
// TODO: implement msvc specific c_options_before_target and c_options_after_target ...
fn (cflags []CFlag) c_options_before_target_msvc() string {

View File

@ -5,16 +5,127 @@ module compile
import (
benchmark
compiler
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) {
// Construct the V object from command line arguments
parse_and_output_new_format(args)
prefs, remaining := parse_arguments(args)
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) {
println(args)
}
@ -26,7 +137,8 @@ pub fn compile(command string, args []string) {
v.compile2()
}
else {
v.compile()
//v.compile()
v.compile2()
}
if v.pref.is_stats {
tmark.stop()
@ -35,10 +147,10 @@ pub fn compile(command string, args []string) {
if v.pref.is_test || v.pref.is_run {
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) {
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)
}
// '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
}

View File

@ -1,4 +1,4 @@
module compiler
module compile
import (
os
@ -20,6 +20,8 @@ fn (v &V) generate_hotcode_reloading_compiler_flags() []string {
}
fn (v &V) generate_hotcode_reloading_declarations() {
/*
QTODO
mut cgen := v.cgen
if v.pref.os != .windows {
if v.pref.is_so {
@ -45,9 +47,12 @@ void pthread_mutex_unlock(HANDLE *m) {
cgen.genln('HANDLE live_fn_mutex = 0;')
}
}
*/
}
fn (v &V) generate_hotcode_reloading_main_caller() {
// QTODO
/*
if !v.pref.is_live {
return
}
@ -72,9 +77,12 @@ fn (v &V) generate_hotcode_reloading_main_caller() {
cgen.genln(' unsigned long _thread_so;')
cgen.genln(' _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);')
}
*/
}
fn (v &V) generate_hot_reload_code() {
/*
QTODO
mut cgen := v.cgen
// Hot code reloading
if v.pref.is_live {
@ -246,4 +254,5 @@ void reload_so() {
if v.pref.is_so {
cgen.genln(' int load_so(byteptr path) { return 0; }')
}
*/
}

View File

@ -1,4 +1,4 @@
module compiler
module compile
import os

View File

@ -4,15 +4,23 @@
module main
import (
compiler
os
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) {
vexe := pref.vexe_path()
vroot := os.dir(vexe)
compiler.set_vroot_folder(vroot)
set_vroot_folder(vroot)
tool_args := os.args[1..].join(' ')
tool_exe := path_of_executable(os.real_path('$vroot/cmd/tools/$tool_name'))

View File

@ -4,7 +4,6 @@
module main
import (
compiler
internal.compile
internal.flag
internal.help
@ -24,6 +23,11 @@ const (
'setup-freetype']
)
pub const (
v_version = '0.1.26'
)
fn main() {
prefs := flag.MainCmdPreferences{}
values := flag.parse_main_cmd(os.args, parse_flags, prefs) or {
@ -32,7 +36,7 @@ fn main() {
exit(1)
}
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) {
println('Parsed preferences: ')
@ -122,11 +126,18 @@ fn main() {
}
fn print_version_and_exit() {
version_hash := compiler.vhash()
println('V $compiler.Version $version_hash')
version_hash := vhash()
println('V $v_version $version_hash')
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) {
match remaining.len {
0, 1 {

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
*/

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
module compiler
// import os
// import compiler.x64

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -384,6 +384,10 @@ fn (g mut Gen) stmt(node ast.Stmt) {
styp := g.typ(it.typ)
g.definitions.writeln('$styp $it.name; // global')
}
ast.GoStmt {
g.writeln('// go')
g.expr(it.expr)
}
ast.GotoLabel {
g.writeln('$it.name:')
}
@ -2534,6 +2538,19 @@ fn comp_if_to_ifdef(name string) string {
}
'no_bounds_checking' {
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 {
verror('bad os ifdef name "$name"')