3191 lines
79 KiB
V
3191 lines
79 KiB
V
// 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 gen
|
|
|
|
import (
|
|
strings
|
|
v.ast
|
|
v.table
|
|
v.depgraph
|
|
v.token
|
|
v.pref
|
|
term
|
|
v.util
|
|
)
|
|
|
|
const (
|
|
c_reserved = ['delete', 'exit', 'unix', 'error', 'calloc', 'malloc', 'free', 'panic', 'auto',
|
|
'char', 'default', 'do', 'double', 'extern', 'float', 'inline', 'int', 'long', 'register',
|
|
'restrict', 'short', 'signed', 'sizeof', 'static', 'switch', 'typedef', 'union', 'unsigned',
|
|
'void', 'volatile', 'while']
|
|
)
|
|
|
|
struct Gen {
|
|
out strings.Builder
|
|
typedefs strings.Builder
|
|
typedefs2 strings.Builder
|
|
definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file)
|
|
inits strings.Builder // contents of `void _vinit(){}`
|
|
gowrappers strings.Builder // all go callsite wrappers
|
|
stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined
|
|
includes strings.Builder
|
|
table &table.Table
|
|
pref &pref.Preferences
|
|
mut:
|
|
file ast.File
|
|
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
|
|
tmp_count int
|
|
variadic_args map[string]int
|
|
is_c_call bool // e.g. `C.printf("v")`
|
|
is_assign_lhs bool // inside left part of assign expr (for array_set(), etc)
|
|
is_assign_rhs bool // inside right part of assign after `=` (val expr)
|
|
is_array_set bool
|
|
is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc
|
|
optionals []string // to avoid duplicates TODO perf, use map
|
|
inside_ternary bool // ?: comma separated statements on a single line
|
|
stmt_start_pos int
|
|
right_is_opt bool
|
|
autofree bool
|
|
indent int
|
|
empty_line bool
|
|
is_test bool
|
|
assign_op token.Kind // *=, =, etc (for array_set)
|
|
defer_stmts []ast.DeferStmt
|
|
defer_ifdef string
|
|
str_types []string // types that need automatic str() generation
|
|
threaded_fns []string // for generating unique wrapper types and fns for `go xxx()`
|
|
array_fn_definitions []string // array equality functions that have been defined
|
|
}
|
|
|
|
const (
|
|
tabs = ['', '\t', '\t\t', '\t\t\t', '\t\t\t\t', '\t\t\t\t\t', '\t\t\t\t\t\t', '\t\t\t\t\t\t\t',
|
|
'\t\t\t\t\t\t\t\t']
|
|
)
|
|
|
|
pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string {
|
|
if true { // if
|
|
x := 10 // line
|
|
// sep
|
|
y := 20
|
|
} else {
|
|
}
|
|
// println('start cgen2')
|
|
mut g := Gen{
|
|
out: strings.new_builder(1000)
|
|
typedefs: strings.new_builder(100)
|
|
typedefs2: strings.new_builder(100)
|
|
definitions: strings.new_builder(100)
|
|
gowrappers: strings.new_builder(100)
|
|
stringliterals: strings.new_builder(100)
|
|
inits: strings.new_builder(100)
|
|
includes: strings.new_builder(100)
|
|
table: table
|
|
pref: pref
|
|
fn_decl: 0
|
|
autofree: true
|
|
indent: -1
|
|
}
|
|
g.init()
|
|
//
|
|
mut autofree_used := false
|
|
for file in files {
|
|
g.file = file
|
|
// println('\ncgen "$g.file.path" nr_stmts=$file.stmts.len')
|
|
building_v := true && (g.file.path.contains('/vlib/') || g.file.path.contains('cmd/v'))
|
|
is_test := g.file.path.ends_with('.vv') || g.file.path.ends_with('_test.v')
|
|
if g.file.path.ends_with('_test.v') {
|
|
g.is_test = is_test
|
|
}
|
|
if g.file.path == '' || is_test || building_v || !g.pref.autofree {
|
|
// cgen test or building V
|
|
// println('autofree=false')
|
|
g.autofree = false
|
|
} else {
|
|
g.autofree = true
|
|
autofree_used = true
|
|
}
|
|
g.stmts(file.stmts)
|
|
}
|
|
if autofree_used {
|
|
g.autofree = true // so that void _vcleanup is generated
|
|
}
|
|
g.write_variadic_types()
|
|
// g.write_str_definitions()
|
|
if g.pref.build_mode != .build_module {
|
|
// no init in builtin.o
|
|
g.write_init_function()
|
|
}
|
|
if g.is_test {
|
|
g.write_tests_main()
|
|
}
|
|
//
|
|
g.finish()
|
|
return g.hashes() + g.includes.str() + g.typedefs.str() + g.typedefs2.str() + g.definitions.str() +
|
|
g.gowrappers.str() + g.stringliterals.str() + g.out.str()
|
|
}
|
|
|
|
pub fn (g Gen) hashes() string {
|
|
mut res := c_commit_hash_default.replace('@@@', util.vhash())
|
|
res += c_current_commit_hash_default.replace('@@@', util.githash(g.pref.building_v))
|
|
return res
|
|
}
|
|
|
|
pub fn (g mut Gen) init() {
|
|
g.definitions.writeln('// Generated by the V compiler')
|
|
g.definitions.writeln('#include <inttypes.h>') // int64_t etc
|
|
g.definitions.writeln(c_builtin_types)
|
|
g.definitions.writeln(c_headers)
|
|
g.definitions.writeln('\nstring _STR(const char*, ...);\n')
|
|
g.definitions.writeln('\nstring _STR_TMP(const char*, ...);\n')
|
|
g.write_builtin_types()
|
|
g.write_typedef_types()
|
|
g.write_typeof_functions()
|
|
if g.pref.build_mode != .build_module {
|
|
// _STR functions should not be defined in builtin.o
|
|
g.write_str_fn_definitions()
|
|
}
|
|
g.write_sorted_types()
|
|
g.write_multi_return_types()
|
|
g.definitions.writeln('// end of definitions #endif')
|
|
//
|
|
g.stringliterals.writeln('')
|
|
g.stringliterals.writeln('// >> string literal consts')
|
|
if g.pref.build_mode != .build_module {
|
|
g.stringliterals.writeln('void vinit_string_literals(){')
|
|
}
|
|
}
|
|
|
|
pub fn (g mut Gen) finish() {
|
|
if g.pref.build_mode != .build_module {
|
|
g.stringliterals.writeln('}')
|
|
}
|
|
g.stringliterals.writeln('// << string literal consts')
|
|
g.stringliterals.writeln('')
|
|
}
|
|
|
|
pub fn (g mut Gen) write_typeof_functions() {
|
|
g.writeln('')
|
|
g.writeln('// >> typeof() support for sum types')
|
|
for typ in g.table.types {
|
|
if typ.kind == .sum_type {
|
|
sum_info := typ.info as table.SumType
|
|
tidx := g.table.find_type_idx(typ.name)
|
|
g.writeln('char * v_typeof_sumtype_${tidx}(int sidx) { /* ${typ.name} */ ')
|
|
g.writeln(' switch(sidx) {')
|
|
g.writeln(' case $tidx: return "$typ.name";')
|
|
for v in sum_info.variants {
|
|
subtype := g.table.get_type_symbol(v)
|
|
g.writeln(' case $v: return "$subtype.name";')
|
|
}
|
|
g.writeln(' default: return "unknown ${typ.name}";')
|
|
g.writeln(' }')
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
g.writeln('// << typeof() support for sum types')
|
|
g.writeln('')
|
|
}
|
|
|
|
// V type to C type
|
|
pub fn (g mut Gen) typ(t table.Type) string {
|
|
nr_muls := table.type_nr_muls(t)
|
|
sym := g.table.get_type_symbol(t)
|
|
mut styp := sym.name.replace('.', '__')
|
|
if nr_muls > 0 {
|
|
styp += strings.repeat(`*`, nr_muls)
|
|
}
|
|
if styp.starts_with('C__') {
|
|
styp = styp[3..]
|
|
if sym.kind == .struct_ {
|
|
info := sym.info as table.Struct
|
|
if !info.is_typedef {
|
|
styp = 'struct $styp'
|
|
}
|
|
}
|
|
}
|
|
if table.type_is(t, .optional) {
|
|
// Register an optional
|
|
styp = 'Option_' + styp
|
|
if !(styp in g.optionals) {
|
|
// println(styp)
|
|
g.typedefs2.writeln('typedef Option $styp;')
|
|
g.optionals << styp
|
|
}
|
|
}
|
|
return styp
|
|
}
|
|
|
|
//
|
|
pub fn (g mut Gen) write_typedef_types() {
|
|
for typ in g.table.types {
|
|
match typ.kind {
|
|
.alias {
|
|
parent := &g.table.types[typ.parent_idx]
|
|
styp := typ.name.replace('.', '__')
|
|
parent_styp := parent.name.replace('.', '__')
|
|
g.definitions.writeln('typedef $parent_styp $styp;')
|
|
}
|
|
.array {
|
|
styp := typ.name.replace('.', '__')
|
|
g.definitions.writeln('typedef array $styp;')
|
|
}
|
|
.map {
|
|
styp := typ.name.replace('.', '__')
|
|
g.definitions.writeln('typedef map $styp;')
|
|
}
|
|
.function {
|
|
info := typ.info as table.FnType
|
|
func := info.func
|
|
if !info.has_decl && !info.is_anon {
|
|
fn_name := if func.is_c { func.name.replace('.', '__') } else { c_name(func.name) }
|
|
g.definitions.write('typedef ${g.typ(func.return_type)} (*$fn_name)(')
|
|
for i, arg in func.args {
|
|
g.definitions.write(g.typ(arg.typ))
|
|
if i < func.args.len - 1 {
|
|
g.definitions.write(',')
|
|
}
|
|
}
|
|
g.definitions.writeln(');')
|
|
}
|
|
}
|
|
else {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (g mut Gen) write_multi_return_types() {
|
|
g.definitions.writeln('// multi return structs')
|
|
for typ in g.table.types {
|
|
// sym := g.table.get_type_symbol(typ)
|
|
if typ.kind != .multi_return {
|
|
continue
|
|
}
|
|
name := typ.name.replace('.', '__')
|
|
info := typ.info as table.MultiReturn
|
|
g.definitions.writeln('typedef struct {')
|
|
// TODO copy pasta StructDecl
|
|
// for field in struct_info.fields {
|
|
for i, mr_typ in info.types {
|
|
type_name := g.typ(mr_typ)
|
|
g.definitions.writeln('\t$type_name arg${i};')
|
|
}
|
|
g.definitions.writeln('} $name;\n')
|
|
// g.typedefs.writeln('typedef struct $name $name;')
|
|
}
|
|
}
|
|
|
|
pub fn (g mut Gen) write_variadic_types() {
|
|
if g.variadic_args.size > 0 {
|
|
g.definitions.writeln('// variadic structs')
|
|
}
|
|
for type_str, arg_len in g.variadic_args {
|
|
typ := table.Type(type_str.int())
|
|
type_name := g.typ(typ)
|
|
struct_name := 'varg_' + type_name.replace('*', '_ptr')
|
|
g.definitions.writeln('struct $struct_name {')
|
|
g.definitions.writeln('\tint len;')
|
|
g.definitions.writeln('\t$type_name args[$arg_len];')
|
|
g.definitions.writeln('};\n')
|
|
g.typedefs.writeln('typedef struct $struct_name $struct_name;')
|
|
}
|
|
}
|
|
|
|
pub fn (g Gen) save() {
|
|
}
|
|
|
|
pub fn (g mut Gen) write(s string) {
|
|
if g.indent > 0 && g.empty_line {
|
|
g.out.write(tabs[g.indent])
|
|
// g.line_len += g.indent * 4
|
|
}
|
|
g.out.write(s)
|
|
g.empty_line = false
|
|
}
|
|
|
|
pub fn (g mut Gen) writeln(s string) {
|
|
if g.indent > 0 && g.empty_line {
|
|
g.out.write(tabs[g.indent])
|
|
}
|
|
g.out.writeln(s)
|
|
g.empty_line = true
|
|
}
|
|
|
|
pub fn (g mut Gen) new_tmp_var() string {
|
|
g.tmp_count++
|
|
return 'tmp$g.tmp_count'
|
|
}
|
|
|
|
pub fn (g mut Gen) reset_tmp_count() {
|
|
g.tmp_count = 0
|
|
}
|
|
|
|
fn (g mut Gen) stmts(stmts []ast.Stmt) {
|
|
g.indent++
|
|
for stmt in stmts {
|
|
g.stmt(stmt)
|
|
// if !g.inside_ternary {
|
|
// g.writeln('')
|
|
// }
|
|
}
|
|
g.indent--
|
|
}
|
|
|
|
fn (g mut Gen) stmt(node ast.Stmt) {
|
|
g.stmt_start_pos = g.out.len
|
|
// println('cgen.stmt()')
|
|
// g.writeln('//// stmt start')
|
|
match node {
|
|
ast.AssertStmt {
|
|
g.gen_assert_stmt(it)
|
|
}
|
|
ast.AssignStmt {
|
|
g.gen_assign_stmt(it)
|
|
}
|
|
ast.Attr {
|
|
if it.name == 'inline' {
|
|
g.writeln(it.name)
|
|
} else {
|
|
g.writeln('//[$it.name]')
|
|
}
|
|
}
|
|
ast.Block {
|
|
g.writeln('{')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
}
|
|
ast.BranchStmt {
|
|
// continue or break
|
|
g.write(it.tok.kind.str())
|
|
g.writeln(';')
|
|
}
|
|
ast.ConstDecl {
|
|
// if g.pref.build_mode != .build_module {
|
|
g.const_decl(it)
|
|
// }
|
|
}
|
|
ast.CompIf {
|
|
g.comp_if(it)
|
|
}
|
|
ast.DeferStmt {
|
|
mut defer_stmt := *it
|
|
defer_stmt.ifdef = g.defer_ifdef
|
|
g.defer_stmts << defer_stmt
|
|
}
|
|
ast.EnumDecl {
|
|
enum_name := it.name.replace('.', '__')
|
|
g.typedefs.writeln('typedef enum {')
|
|
for field in it.fields {
|
|
g.typedefs.write('\t${enum_name}_$field.name')
|
|
if field.has_expr {
|
|
g.typedefs.write(' = ')
|
|
pos := g.out.len
|
|
g.expr(field.expr)
|
|
expr_str := g.out.after(pos)
|
|
g.out.go_back(expr_str.len)
|
|
g.typedefs.write('$expr_str')
|
|
}
|
|
g.typedefs.writeln(',')
|
|
}
|
|
g.typedefs.writeln('} $enum_name;\n')
|
|
}
|
|
ast.ExprStmt {
|
|
g.expr(it.expr)
|
|
expr := it.expr
|
|
match expr {
|
|
ast.IfExpr {
|
|
// no ; after an if expression
|
|
}
|
|
else {
|
|
if !g.inside_ternary {
|
|
g.writeln(';')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ast.FnDecl {
|
|
g.fn_decl = it // &it
|
|
g.gen_fn_decl(it)
|
|
g.writeln('')
|
|
}
|
|
ast.ForCStmt {
|
|
g.write('for (')
|
|
if !it.has_init {
|
|
g.write('; ')
|
|
} else {
|
|
g.stmt(it.init)
|
|
}
|
|
if it.has_cond {
|
|
g.expr(it.cond)
|
|
}
|
|
g.write('; ')
|
|
if it.has_inc {
|
|
g.expr(it.inc)
|
|
}
|
|
g.writeln(') {')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
}
|
|
ast.ForInStmt {
|
|
g.for_in(it)
|
|
}
|
|
ast.ForStmt {
|
|
g.write('while (')
|
|
if it.is_inf {
|
|
g.write('1')
|
|
} else {
|
|
g.expr(it.cond)
|
|
}
|
|
g.writeln(') {')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
}
|
|
ast.GlobalDecl {
|
|
styp := g.typ(it.typ)
|
|
g.definitions.writeln('$styp $it.name; // global')
|
|
}
|
|
ast.GoStmt {
|
|
g.go_stmt(it)
|
|
}
|
|
ast.GotoLabel {
|
|
g.writeln('$it.name:')
|
|
}
|
|
ast.GotoStmt {
|
|
g.writeln('goto $it.name;')
|
|
}
|
|
ast.HashStmt {
|
|
// #include etc
|
|
typ := it.val.all_before(' ')
|
|
if typ in ['include', 'define'] {
|
|
g.definitions.writeln('#$it.val')
|
|
}
|
|
}
|
|
ast.Import {}
|
|
ast.InterfaceDecl {
|
|
g.writeln('// interface')
|
|
}
|
|
ast.Return {
|
|
if g.defer_stmts.len > 0 {
|
|
g.write_defer_stmts()
|
|
}
|
|
g.return_statement(it)
|
|
}
|
|
ast.StructDecl {
|
|
name := if it.is_c { it.name.replace('.', '__') } else { c_name(it.name) }
|
|
// g.writeln('typedef struct {')
|
|
// for field in it.fields {
|
|
// field_type_sym := g.table.get_type_symbol(field.typ)
|
|
// g.writeln('\t$field_type_sym.name $field.name;')
|
|
// }
|
|
// g.writeln('} $name;')
|
|
if it.is_c {
|
|
return
|
|
}
|
|
if it.is_union {
|
|
g.typedefs.writeln('typedef union $name $name;')
|
|
} else {
|
|
g.typedefs.writeln('typedef struct $name $name;')
|
|
}
|
|
}
|
|
ast.TypeDecl {
|
|
g.writeln('// TypeDecl')
|
|
}
|
|
ast.UnsafeStmt {
|
|
g.stmts(it.stmts)
|
|
}
|
|
else {
|
|
verror('cgen.stmt(): unhandled node ' + typeof(node))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) write_defer_stmts() {
|
|
for defer_stmt in g.defer_stmts {
|
|
g.writeln('// defer')
|
|
if defer_stmt.ifdef.len > 0 {
|
|
g.writeln(defer_stmt.ifdef)
|
|
g.stmts(defer_stmt.stmts)
|
|
g.writeln('#endif')
|
|
} else {
|
|
g.stmts(defer_stmt.stmts)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) for_in(it ast.ForInStmt) {
|
|
if it.is_range {
|
|
// `for x in 1..10 {`
|
|
i := g.new_tmp_var()
|
|
g.write('for (int $i = ')
|
|
g.expr(it.cond)
|
|
g.write('; $i < ')
|
|
g.expr(it.high)
|
|
g.writeln('; $i++) {')
|
|
g.writeln('\tint $it.val_var = $i;')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
} else if it.kind == .array {
|
|
// TODO:
|
|
// `for num in nums {`
|
|
g.writeln('// FOR IN')
|
|
i := if it.key_var == '' { g.new_tmp_var() } else { it.key_var }
|
|
styp := g.typ(it.val_type)
|
|
g.write('for (int $i = 0; $i < ')
|
|
g.expr(it.cond)
|
|
cond_type_is_ptr := table.type_is_ptr(it.cond_type)
|
|
if cond_type_is_ptr {
|
|
g.writeln('->')
|
|
} else {
|
|
g.writeln('.')
|
|
}
|
|
g.write('len; $i++) {')
|
|
g.write('\t$styp $it.val_var = (($styp*)')
|
|
g.expr(it.cond)
|
|
if cond_type_is_ptr {
|
|
g.writeln('->')
|
|
} else {
|
|
g.writeln('.')
|
|
}
|
|
g.write('data)[$i];')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
} else if it.kind == .map {
|
|
// `for num in nums {`
|
|
g.writeln('// FOR IN')
|
|
key_styp := g.typ(it.key_type)
|
|
val_styp := g.typ(it.val_type)
|
|
keys_tmp := 'keys_' + g.new_tmp_var()
|
|
idx := g.new_tmp_var()
|
|
key := if it.key_var == '' { g.new_tmp_var() } else { it.key_var }
|
|
zero := g.type_default(it.val_type)
|
|
g.write('array_$key_styp $keys_tmp = map_keys(&')
|
|
g.expr(it.cond)
|
|
g.writeln(');')
|
|
g.writeln('for (int $idx = 0; $idx < ${keys_tmp}.len; $idx++) {')
|
|
g.writeln('\t$key_styp $key = (($key_styp*)${keys_tmp}.data)[$idx];')
|
|
g.write('\t$val_styp $it.val_var = (*($val_styp*)map_get3(')
|
|
g.expr(it.cond)
|
|
g.writeln(', $key, &($val_styp[]){ $zero }));')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
} else if table.type_is(it.cond_type, .variadic) {
|
|
g.writeln('// FOR IN')
|
|
i := if it.key_var == '' { g.new_tmp_var() } else { it.key_var }
|
|
styp := g.typ(it.cond_type)
|
|
g.write('for (int $i = 0; $i < ')
|
|
g.expr(it.cond)
|
|
g.writeln('.len; $i++) {')
|
|
g.write('$styp $it.val_var = ')
|
|
g.expr(it.cond)
|
|
g.writeln('.args[$i];')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
} else if it.kind == .string {
|
|
i := if it.key_var == '' { g.new_tmp_var() } else { it.key_var }
|
|
g.write('for (int $i = 0; $i < ')
|
|
g.expr(it.cond)
|
|
g.writeln('.len; $i++) {')
|
|
g.write('byte $it.val_var = ')
|
|
g.expr(it.cond)
|
|
g.writeln('.str[$i];')
|
|
g.stmts(it.stmts)
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
|
|
// use instead of expr() when you need to cast to sum type (can add other casts also)
|
|
fn (g mut Gen) expr_with_cast(expr ast.Expr, got_type, exp_type table.Type) {
|
|
// cast to sum type
|
|
if exp_type != table.void_type {
|
|
exp_sym := g.table.get_type_symbol(exp_type)
|
|
if exp_sym.kind == .sum_type {
|
|
sum_info := exp_sym.info as table.SumType
|
|
if got_type in sum_info.variants {
|
|
got_sym := g.table.get_type_symbol(got_type)
|
|
got_styp := g.typ(got_type)
|
|
exp_styp := g.typ(exp_type)
|
|
got_idx := table.type_idx(got_type)
|
|
g.write('/* sum type cast */ ($exp_styp) {.obj = memdup(&(${got_styp}[]) {')
|
|
g.expr(expr)
|
|
g.write('}, sizeof($got_styp)), .typ = $got_idx /* $got_sym.name */}')
|
|
return
|
|
}
|
|
}
|
|
}
|
|
// no cast
|
|
g.expr(expr)
|
|
}
|
|
|
|
fn (g mut Gen) gen_assert_stmt(a ast.AssertStmt) {
|
|
g.writeln('// assert')
|
|
g.write('if( ')
|
|
g.expr(a.expr)
|
|
g.write(' )')
|
|
s_assertion := a.expr.str().replace('"', "\'")
|
|
mut mod_path := g.file.path
|
|
$if windows {
|
|
mod_path = g.file.path.replace('\\', '\\\\')
|
|
}
|
|
if g.is_test {
|
|
g.writeln('{')
|
|
g.writeln(' g_test_oks++;')
|
|
g.writeln(' cb_assertion_ok( _STR("${mod_path}"), ${a.pos.line_nr+1}, _STR("assert ${s_assertion}"), _STR("${g.fn_decl.name}()") );')
|
|
g.writeln('}else{')
|
|
g.writeln(' g_test_fails++;')
|
|
g.writeln(' cb_assertion_failed( _STR("${mod_path}"), ${a.pos.line_nr+1}, _STR("assert ${s_assertion}"), _STR("${g.fn_decl.name}()") );')
|
|
g.writeln(' exit(1);')
|
|
g.writeln(' // TODO')
|
|
g.writeln(' // Maybe print all vars in a test function if it fails?')
|
|
g.writeln('}')
|
|
return
|
|
}
|
|
g.writeln('{}else{')
|
|
g.writeln(' eprintln(_STR("${mod_path}:${a.pos.line_nr+1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion"));')
|
|
g.writeln(' exit(1);')
|
|
g.writeln('}')
|
|
}
|
|
|
|
fn (g mut Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
|
// g.write('/*assign_stmt*/')
|
|
if assign_stmt.is_static {
|
|
g.write('static ')
|
|
}
|
|
if assign_stmt.left.len > assign_stmt.right.len {
|
|
// multi return
|
|
mut or_stmts := []ast.Stmt
|
|
mut return_type := table.void_type
|
|
match assign_stmt.right[0] {
|
|
ast.CallExpr {
|
|
or_stmts = it.or_block.stmts
|
|
return_type = it.return_type
|
|
}
|
|
else {}
|
|
}
|
|
is_optional := table.type_is(return_type, .optional)
|
|
mr_var_name := 'mr_$assign_stmt.pos.pos'
|
|
mr_styp := g.typ(return_type)
|
|
g.write('$mr_styp $mr_var_name = ')
|
|
g.is_assign_rhs = true
|
|
g.expr(assign_stmt.right[0])
|
|
g.is_assign_rhs = false
|
|
if is_optional {
|
|
g.or_block(mr_var_name, or_stmts, return_type)
|
|
}
|
|
g.writeln(';')
|
|
for i, ident in assign_stmt.left {
|
|
if ident.kind == .blank_ident {
|
|
continue
|
|
}
|
|
ident_var_info := ident.var_info()
|
|
styp := g.typ(ident_var_info.typ)
|
|
if assign_stmt.op == .decl_assign {
|
|
g.write('$styp ')
|
|
}
|
|
g.expr(ident)
|
|
if is_optional {
|
|
mr_styp2 := mr_styp[7..] // remove Option_
|
|
g.writeln(' = (*(${mr_styp2}*)${mr_var_name}.data).arg$i;')
|
|
} else {
|
|
g.writeln(' = ${mr_var_name}.arg$i;')
|
|
}
|
|
}
|
|
} else {
|
|
// `a := 1` | `a,b := 1,2`
|
|
for i, ident in assign_stmt.left {
|
|
val := assign_stmt.right[i]
|
|
ident_var_info := ident.var_info()
|
|
styp := g.typ(ident_var_info.typ)
|
|
mut is_call := false
|
|
mut or_stmts := []ast.Stmt
|
|
mut return_type := table.void_type
|
|
match val {
|
|
ast.CallExpr {
|
|
is_call = true
|
|
or_stmts = it.or_block.stmts
|
|
return_type = it.return_type
|
|
}
|
|
else {}
|
|
}
|
|
gen_or := is_call && table.type_is(return_type, .optional)
|
|
g.is_assign_rhs = true
|
|
if ident.kind == .blank_ident {
|
|
if is_call {
|
|
g.expr(val)
|
|
} else {
|
|
g.write('{$styp _ = ')
|
|
g.expr(val)
|
|
g.writeln(';}')
|
|
}
|
|
} else {
|
|
right_sym := g.table.get_type_symbol(assign_stmt.right_types[i])
|
|
mut is_fixed_array_init := false
|
|
match val {
|
|
ast.ArrayInit {
|
|
is_fixed_array_init = it.is_fixed
|
|
}
|
|
else {}
|
|
}
|
|
is_decl := assign_stmt.op == .decl_assign
|
|
// g.write('/*assign_stmt*/')
|
|
if is_decl {
|
|
g.write('$styp ')
|
|
}
|
|
g.expr(ident)
|
|
if g.autofree && right_sym.kind in [.array, .string] {
|
|
if g.gen_clone_assignment(val, right_sym, true) {
|
|
g.writeln(';')
|
|
// g.expr_var_name = ''
|
|
return
|
|
}
|
|
}
|
|
if is_fixed_array_init {
|
|
g.write('= {0}')
|
|
} else {
|
|
g.write(' = ')
|
|
if !is_decl {
|
|
g.expr_with_cast(val, assign_stmt.left_types[i], ident_var_info.typ)
|
|
} else {
|
|
g.expr(val)
|
|
}
|
|
}
|
|
if gen_or {
|
|
g.or_block(ident.name, or_stmts, return_type)
|
|
}
|
|
}
|
|
g.is_assign_rhs = false
|
|
g.writeln(';')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) gen_clone_assignment(val ast.Expr, right_sym table.TypeSymbol, add_eq bool) bool {
|
|
mut is_ident := false
|
|
match val {
|
|
ast.Ident {
|
|
is_ident = true
|
|
}
|
|
ast.SelectorExpr {
|
|
is_ident = true
|
|
}
|
|
else {
|
|
return false
|
|
}
|
|
}
|
|
if g.autofree && right_sym.kind == .array && is_ident {
|
|
// `arr1 = arr2` => `arr1 = arr2.clone()`
|
|
if add_eq {
|
|
g.write('=')
|
|
}
|
|
g.write(' array_clone(&')
|
|
g.expr(val)
|
|
g.write(')')
|
|
} else if g.autofree && right_sym.kind == .string && is_ident {
|
|
if add_eq {
|
|
g.write('=')
|
|
}
|
|
// `str1 = str2` => `str1 = str2.clone()`
|
|
g.write(' string_clone(')
|
|
g.expr(val)
|
|
g.write(')')
|
|
}
|
|
return true
|
|
}
|
|
|
|
fn (g mut Gen) gen_fn_decl(it ast.FnDecl) {
|
|
if it.is_c {
|
|
// || it.no_body {
|
|
return
|
|
}
|
|
g.reset_tmp_count()
|
|
is_main := it.name == 'main'
|
|
if is_main {
|
|
if g.pref.os == .windows {
|
|
g.write('int wmain(int ___argc, wchar_t *___argv[], wchar_t *___envp[]')
|
|
} else {
|
|
g.write('int ${it.name}(int ___argc, char** ___argv')
|
|
}
|
|
} else {
|
|
mut name := it.name
|
|
c := name[0]
|
|
if c in [`+`, `-`, `*`, `/`, `%`] {
|
|
name = util.replace_op(name)
|
|
}
|
|
if it.is_method {
|
|
name = g.table.get_type_symbol(it.receiver.typ).name + '_' + name
|
|
}
|
|
if it.is_c {
|
|
name = name.replace('.', '__')
|
|
} else {
|
|
name = c_name(name)
|
|
}
|
|
// if g.pref.show_cc && it.is_builtin {
|
|
// println(name)
|
|
// }
|
|
// type_name := g.table.Type_to_str(it.return_type)
|
|
type_name := g.typ(it.return_type)
|
|
g.write('$type_name ${name}(')
|
|
g.definitions.write('$type_name ${name}(')
|
|
}
|
|
// Receiver is the first argument
|
|
/*
|
|
if it.is_method {
|
|
mut styp := g.typ(it.receiver.typ)
|
|
// if table.type_nr_muls(it.receiver.typ) > 0 {
|
|
// if it.rec_mut {
|
|
// styp += '*'
|
|
// }
|
|
g.write('$styp $it.receiver.name ')
|
|
// TODO mut
|
|
g.definitions.write('$styp $it.receiver.name')
|
|
if it.args.len > 0 {
|
|
g.write(', ')
|
|
g.definitions.write(', ')
|
|
}
|
|
}
|
|
*/
|
|
//
|
|
g.fn_args(it.args, it.is_variadic)
|
|
if it.no_body || (g.pref.show_cc && it.is_builtin) {
|
|
// Just a function header.
|
|
// Builtin function bodies are defined in builtin.o
|
|
g.definitions.writeln(');')
|
|
g.writeln(');')
|
|
return
|
|
}
|
|
g.writeln(') {')
|
|
if !is_main {
|
|
g.definitions.writeln(');')
|
|
}
|
|
if is_main {
|
|
g.writeln('\t_vinit();')
|
|
if g.is_importing_os() {
|
|
if g.autofree {
|
|
g.writeln('free(_const_os__args.data); // empty, inited in _vinit()')
|
|
}
|
|
if g.pref.os == .windows {
|
|
g.writeln('\t_const_os__args = os__init_os_args_wide(___argc, ___argv);')
|
|
} else {
|
|
g.writeln('\t_const_os__args = os__init_os_args(___argc, (byteptr*)___argv);')
|
|
}
|
|
}
|
|
}
|
|
g.stmts(it.stmts)
|
|
// ////////////
|
|
if g.autofree {
|
|
g.free_scope_vars(it.pos.pos - 1)
|
|
}
|
|
// /////////
|
|
if is_main {
|
|
if g.autofree {
|
|
g.writeln('\t_vcleanup();')
|
|
}
|
|
if g.is_test {
|
|
verror('test files cannot have function `main`')
|
|
}
|
|
g.writeln('\treturn 0;')
|
|
}
|
|
if g.defer_stmts.len > 0 {
|
|
g.write_defer_stmts()
|
|
}
|
|
g.writeln('}')
|
|
g.defer_stmts = []
|
|
g.fn_decl = 0
|
|
}
|
|
|
|
fn (g mut Gen) free_scope_vars(pos int) {
|
|
scope := g.file.scope.innermost(pos)
|
|
for _, obj in scope.objects {
|
|
match obj {
|
|
ast.Var {
|
|
// println('//////')
|
|
// println(var.name)
|
|
// println(var.typ)
|
|
// if var.typ == 0 {
|
|
// // TODO why 0?
|
|
// continue
|
|
// }
|
|
var := *it
|
|
sym := g.table.get_type_symbol(var.typ)
|
|
is_optional := table.type_is(var.typ, .optional)
|
|
if sym.kind == .array && !is_optional {
|
|
g.writeln('array_free($var.name); // autofreed')
|
|
}
|
|
if sym.kind == .string && !is_optional {
|
|
// Don't free simple string literals.
|
|
t := typeof(var.expr)
|
|
match var.expr {
|
|
ast.StringLiteral {
|
|
g.writeln('// str literal')
|
|
continue
|
|
}
|
|
else {
|
|
// NOTE/TODO: assign_stmt multi returns variables have no expr
|
|
// since the type comes from the called fns return type
|
|
g.writeln('// other ' + t)
|
|
continue
|
|
}
|
|
}
|
|
g.writeln('string_free($var.name); // autofreed')
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) fn_args(args []table.Arg, is_variadic bool) {
|
|
no_names := args.len > 0 && args[0].name == 'arg_1'
|
|
for i, arg in args {
|
|
arg_type_sym := g.table.get_type_symbol(arg.typ)
|
|
mut arg_type_name := g.typ(arg.typ) // arg_type_sym.name.replace('.', '__')
|
|
is_varg := i == args.len - 1 && is_variadic
|
|
if is_varg {
|
|
varg_type_str := int(arg.typ).str()
|
|
if !(varg_type_str in g.variadic_args) {
|
|
g.variadic_args[varg_type_str] = 0
|
|
}
|
|
arg_type_name = 'varg_' + g.typ(arg.typ).replace('*', '_ptr')
|
|
}
|
|
if arg_type_sym.kind == .function {
|
|
info := arg_type_sym.info as table.FnType
|
|
func := info.func
|
|
if !info.is_anon {
|
|
g.write(arg_type_name + ' ' + arg.name)
|
|
g.definitions.write(arg_type_name + ' ' + arg.name)
|
|
} else {
|
|
g.write('${g.typ(func.return_type)} (*$arg.name)(')
|
|
g.definitions.write('${g.typ(func.return_type)} (*$arg.name)(')
|
|
g.fn_args(func.args, func.is_variadic)
|
|
g.write(')')
|
|
g.definitions.write(')')
|
|
}
|
|
} else if no_names {
|
|
g.write(arg_type_name)
|
|
g.definitions.write(arg_type_name)
|
|
} else {
|
|
mut nr_muls := table.type_nr_muls(arg.typ)
|
|
s := arg_type_name + ' ' + arg.name
|
|
if arg.is_mut {
|
|
// mut arg needs one *
|
|
nr_muls = 1
|
|
}
|
|
// if nr_muls > 0 && !is_varg {
|
|
// s = arg_type_name + strings.repeat(`*`, nr_muls) + ' ' + arg.name
|
|
// }
|
|
g.write(s)
|
|
g.definitions.write(s)
|
|
}
|
|
if i < args.len - 1 {
|
|
g.write(', ')
|
|
g.definitions.write(', ')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) expr(node ast.Expr) {
|
|
// println('cgen expr() line_nr=$node.pos.line_nr')
|
|
match node {
|
|
ast.ArrayInit {
|
|
type_sym := g.table.get_type_symbol(it.typ)
|
|
if type_sym.kind != .array_fixed {
|
|
elem_sym := g.table.get_type_symbol(it.elem_type)
|
|
elem_type_str := g.typ(it.elem_type)
|
|
if it.exprs.len == 0 {
|
|
g.write('new_array($it.exprs.len, $it.exprs.len, sizeof($elem_type_str))')
|
|
} else {
|
|
len := it.exprs.len
|
|
g.write('new_array_from_c_array($len, $len, sizeof($elem_type_str), ')
|
|
g.write('($elem_type_str[$len]){\n\t\t')
|
|
for expr in it.exprs {
|
|
g.expr(expr)
|
|
g.write(', ')
|
|
}
|
|
g.write('\n})')
|
|
}
|
|
} else {
|
|
}
|
|
}
|
|
ast.AsCast {
|
|
styp := g.typ(it.typ)
|
|
expr_type_sym := g.table.get_type_symbol(it.expr_type)
|
|
if expr_type_sym.kind == .sum_type {
|
|
g.write('/* as */ *($styp*)')
|
|
g.expr(it.expr)
|
|
g.write('.obj')
|
|
}
|
|
}
|
|
ast.AssignExpr {
|
|
g.assign_expr(it)
|
|
}
|
|
ast.Assoc {
|
|
g.assoc(it)
|
|
}
|
|
ast.BoolLiteral {
|
|
g.write(it.val.str())
|
|
}
|
|
ast.CallExpr {
|
|
g.call_expr(it)
|
|
}
|
|
ast.CastExpr {
|
|
// g.write('/*cast*/')
|
|
if g.is_amp {
|
|
// &Foo(0) => ((Foo*)0)
|
|
g.out.go_back(1)
|
|
}
|
|
sym := g.table.get_type_symbol(it.typ)
|
|
if sym.kind == .string && !table.type_is_ptr(it.typ) {
|
|
// `string(x)` needs `tos()`, but not `&string(x)
|
|
// `tos(str, len)`, `tos2(str)`
|
|
if it.has_arg {
|
|
g.write('tos(')
|
|
} else {
|
|
g.write('tos2(')
|
|
}
|
|
g.expr(it.expr)
|
|
expr_sym := g.table.get_type_symbol(it.expr_type)
|
|
if expr_sym.kind == .array {
|
|
// if we are casting an array, we need to add `.data`
|
|
g.write('.data')
|
|
}
|
|
if it.has_arg {
|
|
// len argument
|
|
g.write(', ')
|
|
g.expr(it.arg)
|
|
}
|
|
g.write(')')
|
|
} else if sym.kind == .sum_type {
|
|
g.expr_with_cast(it.expr, it.expr_type, it.typ)
|
|
} else {
|
|
// styp := g.table.Type_to_str(it.typ)
|
|
styp := g.typ(it.typ)
|
|
// g.write('($styp)(')
|
|
g.write('(($styp)(')
|
|
// if g.is_amp {
|
|
// g.write('*')
|
|
// }
|
|
// g.write(')(')
|
|
g.expr(it.expr)
|
|
g.write('))')
|
|
}
|
|
}
|
|
ast.CharLiteral {
|
|
g.write("'$it.val'")
|
|
}
|
|
ast.EnumVal {
|
|
// g.write('/*EnumVal*/${it.mod}${it.enum_name}_$it.val')
|
|
styp := g.typ(it.typ)
|
|
g.write(styp)
|
|
g.write('_$it.val')
|
|
}
|
|
ast.FloatLiteral {
|
|
g.write(it.val)
|
|
}
|
|
ast.Ident {
|
|
g.ident(it)
|
|
}
|
|
ast.IfExpr {
|
|
g.if_expr(it)
|
|
}
|
|
ast.IfGuardExpr {
|
|
g.write('/* guard */')
|
|
}
|
|
ast.IndexExpr {
|
|
g.index_expr(it)
|
|
}
|
|
ast.InfixExpr {
|
|
g.infix_expr(it)
|
|
}
|
|
ast.IntegerLiteral {
|
|
if it.val.starts_with('0o') {
|
|
g.write('0')
|
|
g.write(it.val[2..])
|
|
} else {
|
|
g.write(it.val) // .int().str())
|
|
}
|
|
}
|
|
ast.MatchExpr {
|
|
g.match_expr(it)
|
|
}
|
|
ast.MapInit {
|
|
key_typ_sym := g.table.get_type_symbol(it.key_type)
|
|
value_typ_sym := g.table.get_type_symbol(it.value_type)
|
|
key_typ_str := key_typ_sym.name.replace('.', '__')
|
|
value_typ_str := value_typ_sym.name.replace('.', '__')
|
|
size := it.vals.len
|
|
if size > 0 {
|
|
g.write('new_map_init($size, sizeof($value_typ_str), (${key_typ_str}[$size]){')
|
|
for expr in it.keys {
|
|
g.expr(expr)
|
|
g.write(', ')
|
|
}
|
|
g.write('}, (${value_typ_str}[$size]){')
|
|
for expr in it.vals {
|
|
g.expr(expr)
|
|
g.write(', ')
|
|
}
|
|
g.write('})')
|
|
} else {
|
|
g.write('new_map(1, sizeof($value_typ_str))')
|
|
}
|
|
}
|
|
ast.None {
|
|
g.write('opt_none()')
|
|
}
|
|
ast.ParExpr {
|
|
g.write('(')
|
|
g.expr(it.expr)
|
|
g.write(')')
|
|
}
|
|
ast.PostfixExpr {
|
|
g.expr(it.expr)
|
|
g.write(it.op.str())
|
|
}
|
|
ast.PrefixExpr {
|
|
if it.op == .amp {
|
|
g.is_amp = true
|
|
}
|
|
// g.write('/*pref*/')
|
|
g.write(it.op.str())
|
|
// g.write('(')
|
|
g.expr(it.right)
|
|
// g.write(')')
|
|
g.is_amp = false
|
|
}
|
|
ast.SizeOf {
|
|
if it.type_name != '' {
|
|
g.write('sizeof($it.type_name)')
|
|
} else {
|
|
styp := g.typ(it.typ)
|
|
g.write('sizeof($styp)')
|
|
}
|
|
}
|
|
ast.StringLiteral {
|
|
if it.is_raw {
|
|
escaped_val := it.val.replace_each(['"', '\\"', '\\', '\\\\'])
|
|
g.write('tos3("$escaped_val")')
|
|
return
|
|
}
|
|
escaped_val := it.val.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n'])
|
|
if g.is_c_call || it.is_c {
|
|
// In C calls we have to generate C strings
|
|
// `C.printf("hi")` => `printf("hi");`
|
|
g.write('"$escaped_val"')
|
|
} else {
|
|
g.write('tos3("$escaped_val")')
|
|
}
|
|
}
|
|
ast.StringInterLiteral {
|
|
g.string_inter_literal(it)
|
|
}
|
|
ast.StructInit {
|
|
// `user := User{name: 'Bob'}`
|
|
g.struct_init(it)
|
|
}
|
|
ast.SelectorExpr {
|
|
g.expr(it.expr)
|
|
// if table.type_nr_muls(it.expr_type) > 0 {
|
|
if table.type_is_ptr(it.expr_type) {
|
|
g.write('->')
|
|
} else {
|
|
// g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /')
|
|
g.write('.')
|
|
}
|
|
if it.expr_type == 0 {
|
|
verror('cgen: SelectorExpr typ=0 field=$it.field')
|
|
}
|
|
g.write(c_name(it.field))
|
|
}
|
|
ast.Type {
|
|
// match sum Type
|
|
// g.write('/* Type */')
|
|
type_idx := table.type_idx(it.typ)
|
|
sym := g.table.get_type_symbol(it.typ)
|
|
g.write('$type_idx /* $sym.name */')
|
|
}
|
|
ast.TypeOf {
|
|
g.typeof_expr(it)
|
|
}
|
|
else {
|
|
// #printf("node=%d\n", node.typ);
|
|
println(term.red('cgen.expr(): bad node ' + typeof(node)))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) typeof_expr(node ast.TypeOf) {
|
|
sym := g.table.get_type_symbol(node.expr_type)
|
|
if sym.kind == .sum_type {
|
|
// When encountering a .sum_type, typeof() should be done at runtime,
|
|
// because the subtype of the expression may change:
|
|
sum_type_idx := table.type_idx(node.expr_type)
|
|
g.write('tos3( /* ${sym.name} */ v_typeof_sumtype_${sum_type_idx}( (')
|
|
g.expr(node.expr)
|
|
g.write(').typ ))')
|
|
} else if sym.kind == .array_fixed {
|
|
fixed_info := sym.info as table.ArrayFixed
|
|
elem_sym := g.table.get_type_symbol(fixed_info.elem_type)
|
|
g.write('tos3("[$fixed_info.size]${elem_sym.name}")')
|
|
} else {
|
|
g.write('tos3("${sym.name}")')
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) enum_expr(node ast.Expr) {
|
|
match node {
|
|
ast.EnumVal {
|
|
g.write(it.val)
|
|
}
|
|
else {
|
|
g.expr(node)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) assign_expr(node ast.AssignExpr) {
|
|
// g.write('/*assign_expr*/')
|
|
mut is_call := false
|
|
mut or_stmts := []ast.Stmt
|
|
mut return_type := table.void_type
|
|
match node.val {
|
|
ast.CallExpr {
|
|
is_call = true
|
|
or_stmts = it.or_block.stmts
|
|
return_type = it.return_type
|
|
}
|
|
else {}
|
|
}
|
|
gen_or := is_call && table.type_is(return_type, .optional)
|
|
tmp_opt := if gen_or { g.new_tmp_var() } else { '' }
|
|
if gen_or {
|
|
rstyp := g.typ(return_type)
|
|
g.write('$rstyp $tmp_opt =')
|
|
}
|
|
g.is_assign_rhs = true
|
|
if ast.expr_is_blank_ident(node.left) {
|
|
if is_call {
|
|
g.expr(node.val)
|
|
} else {
|
|
// g.write('{${g.typ(node.left_type)} _ = ')
|
|
g.write('{')
|
|
g.expr(node.val)
|
|
g.writeln(';}')
|
|
}
|
|
} else {
|
|
g.is_assign_lhs = true
|
|
if table.type_is(node.right_type, .optional) {
|
|
g.right_is_opt = true
|
|
}
|
|
mut str_add := false
|
|
if node.left_type == table.string_type_idx && node.op == .plus_assign {
|
|
// str += str2 => `str = string_add(str, str2)`
|
|
g.expr(node.left)
|
|
g.write(' = string_add(')
|
|
str_add = true
|
|
}
|
|
g.assign_op = node.op
|
|
g.expr(node.left)
|
|
// arr[i] = val => `array_set(arr, i, val)`, not `array_get(arr, i) = val`
|
|
if !g.is_array_set && !str_add {
|
|
g.write(' $node.op.str() ')
|
|
} else if str_add {
|
|
g.write(', ')
|
|
}
|
|
g.is_assign_lhs = false
|
|
right_sym := g.table.get_type_symbol(node.right_type)
|
|
// left_sym := g.table.get_type_symbol(node.left_type)
|
|
mut cloned := false
|
|
// !g.is_array_set
|
|
if g.autofree && right_sym.kind in [.array, .string] {
|
|
if g.gen_clone_assignment(node.val, right_sym, false) {
|
|
cloned = true
|
|
}
|
|
}
|
|
if !cloned {
|
|
g.expr_with_cast(node.val, node.right_type, node.left_type)
|
|
}
|
|
if g.is_array_set {
|
|
g.write(' })')
|
|
g.is_array_set = false
|
|
} else if str_add {
|
|
g.write(')')
|
|
}
|
|
g.right_is_opt = false
|
|
}
|
|
if gen_or {
|
|
g.or_block(tmp_opt, or_stmts, return_type)
|
|
}
|
|
g.is_assign_rhs = false
|
|
}
|
|
|
|
fn (g mut Gen) infix_expr(node ast.InfixExpr) {
|
|
// println('infix_expr() op="$node.op.str()" line_nr=$node.pos.line_nr')
|
|
// g.write('/*infix*/')
|
|
// if it.left_type == table.string_type_idx {
|
|
// g.write('/*$node.left_type str*/')
|
|
// }
|
|
// string + string, string == string etc
|
|
// g.infix_op = node.op
|
|
left_sym := g.table.get_type_symbol(node.left_type)
|
|
right_sym := g.table.get_type_symbol(node.right_type)
|
|
if node.left_type == table.string_type_idx && node.op != .key_in {
|
|
fn_name := match node.op {
|
|
.plus {
|
|
'string_add('
|
|
}
|
|
.eq {
|
|
'string_eq('
|
|
}
|
|
.ne {
|
|
'string_ne('
|
|
}
|
|
.lt {
|
|
'string_lt('
|
|
}
|
|
.le {
|
|
'string_le('
|
|
}
|
|
.gt {
|
|
'string_gt('
|
|
}
|
|
.ge {
|
|
'string_ge('
|
|
}
|
|
else {
|
|
'/*node error*/'
|
|
}
|
|
}
|
|
g.write(fn_name)
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
} else if node.op in [.eq, .ne] && left_sym.kind == .array && right_sym.kind == .array {
|
|
styp := g.table.value_type(node.left_type)
|
|
ptr_typ := g.typ(node.left_type).split('_')[1]
|
|
if !(ptr_typ in g.array_fn_definitions) {
|
|
sym := g.table.get_type_symbol(left_sym.array_info().elem_type)
|
|
g.generate_array_equality_fn(ptr_typ, styp, sym)
|
|
}
|
|
if node.op == .eq {
|
|
g.write('${ptr_typ}_arr_eq(')
|
|
} else if node.op == .ne {
|
|
g.write('!${ptr_typ}_arr_eq(')
|
|
}
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
} else if node.op == .key_in {
|
|
if right_sym.kind == .array {
|
|
match node.right {
|
|
ast.ArrayInit {
|
|
// `a in [1,2,3]` optimization => `a == 1 || a == 2 || a == 3`
|
|
// avoids an allocation
|
|
// g.write('/*in opt*/')
|
|
g.write('(')
|
|
g.in_optimization(node.left, it)
|
|
g.write(')')
|
|
return
|
|
}
|
|
else {}
|
|
}
|
|
styp := g.typ(node.left_type)
|
|
g.write('_IN($styp, ')
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
} else if right_sym.kind == .map {
|
|
g.write('_IN_MAP(')
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
} else if right_sym.kind == .string {
|
|
g.write('string_contains(')
|
|
g.expr(node.right)
|
|
g.write(', ')
|
|
g.expr(node.left)
|
|
g.write(')')
|
|
}
|
|
} else if node.op == .left_shift && g.table.get_type_symbol(node.left_type).kind == .array {
|
|
// arr << val
|
|
tmp := g.new_tmp_var()
|
|
sym := g.table.get_type_symbol(node.left_type)
|
|
info := sym.info as table.Array
|
|
if right_sym.kind == .array && info.elem_type != node.right_type {
|
|
// push an array => PUSH_MANY, but not if pushing an array to 2d array (`[][]int << []int`)
|
|
g.write('_PUSH_MANY(&')
|
|
g.expr_with_cast(node.left, node.right_type, node.left_type)
|
|
g.write(', (')
|
|
g.expr(node.right)
|
|
styp := g.typ(node.left_type)
|
|
g.write('), $tmp, $styp)')
|
|
} else {
|
|
// push a single element
|
|
elem_type_str := g.typ(info.elem_type)
|
|
// g.write('array_push(&')
|
|
g.write('_PUSH(&')
|
|
g.expr_with_cast(node.left, node.right_type, info.elem_type)
|
|
g.write(', (')
|
|
g.expr(node.right)
|
|
g.write('), $tmp, $elem_type_str)')
|
|
}
|
|
} else if (node.left_type == node.right_type) && node.left_type in [table.f32_type_idx,
|
|
table.f64_type_idx] && node.op in [.eq, .ne] {
|
|
// floats should be compared with epsilon
|
|
if node.left_type == table.f64_type_idx {
|
|
if node.op == .eq {
|
|
g.write('f64_eq(')
|
|
} else {
|
|
g.write('f64_ne(')
|
|
}
|
|
} else {
|
|
if node.op == .eq {
|
|
g.write('f32_eq(')
|
|
} else {
|
|
g.write('f32_ne(')
|
|
}
|
|
}
|
|
g.expr(node.left)
|
|
g.write(',')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
} else if node.op in [.plus, .minus, .mul, .div, .mod] && (left_sym.name[0].is_capital() ||
|
|
left_sym.name.contains('.')) && left_sym.kind != .alias {
|
|
// !left_sym.is_number() {
|
|
g.write(g.typ(node.left_type))
|
|
g.write('_')
|
|
g.write(util.replace_op(node.op.str()))
|
|
g.write('(')
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
} else {
|
|
need_par := node.op in [.amp, .pipe, .xor] // `x & y == 0` => `(x & y) == 0` in C
|
|
if need_par {
|
|
g.write('(')
|
|
}
|
|
g.expr(node.left)
|
|
g.write(' $node.op.str() ')
|
|
g.expr(node.right)
|
|
if need_par {
|
|
g.write(')')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) match_expr(node ast.MatchExpr) {
|
|
// println('match expr typ=$it.expr_type')
|
|
// TODO
|
|
if node.cond_type == 0 {
|
|
g.writeln('// match 0')
|
|
return
|
|
}
|
|
is_expr := node.is_expr && node.return_type != table.void_type
|
|
if is_expr {
|
|
g.inside_ternary = true
|
|
// g.write('/* EM ret type=${g.typ(node.return_type)} expected_type=${g.typ(node.expected_type)} */')
|
|
}
|
|
type_sym := g.table.get_type_symbol(node.cond_type)
|
|
mut tmp := ''
|
|
if type_sym.kind != .void {
|
|
tmp = g.new_tmp_var()
|
|
}
|
|
// styp := g.typ(node.expr_type)
|
|
// g.write('$styp $tmp = ')
|
|
// g.expr(node.cond)
|
|
// g.writeln(';') // $it.blocks.len')
|
|
// mut sum_type_str = ''
|
|
for j, branch in node.branches {
|
|
if j == node.branches.len - 1 {
|
|
// last block is an `else{}`
|
|
if is_expr {
|
|
// TODO too many branches. maybe separate ?: matches
|
|
g.write(' : ')
|
|
} else {
|
|
g.writeln('else {')
|
|
}
|
|
} else {
|
|
if j > 0 {
|
|
if is_expr {
|
|
g.write(' : ')
|
|
} else {
|
|
g.write('else ')
|
|
}
|
|
}
|
|
if is_expr {
|
|
g.write('(')
|
|
} else {
|
|
g.write('if (')
|
|
}
|
|
for i, expr in branch.exprs {
|
|
if node.is_sum_type {
|
|
g.expr(node.cond)
|
|
g.write('.typ == ')
|
|
// g.write('${tmp}.typ == ')
|
|
// sum_type_str
|
|
} else if type_sym.kind == .string {
|
|
g.write('string_eq(')
|
|
//
|
|
g.expr(node.cond)
|
|
g.write(', ')
|
|
// g.write('string_eq($tmp, ')
|
|
} else {
|
|
g.expr(node.cond)
|
|
g.write(' == ')
|
|
// g.write('$tmp == ')
|
|
}
|
|
g.expr(expr)
|
|
if type_sym.kind == .string {
|
|
g.write(')')
|
|
}
|
|
if i < branch.exprs.len - 1 {
|
|
g.write(' || ')
|
|
}
|
|
}
|
|
if is_expr {
|
|
g.write(') ? ')
|
|
} else {
|
|
g.writeln(') {')
|
|
}
|
|
}
|
|
// g.writeln('/* M sum_type=$node.is_sum_type is_expr=$node.is_expr exp_type=${g.typ(node.expected_type)}*/')
|
|
if node.is_sum_type && branch.exprs.len > 0 && !node.is_expr {
|
|
// The first node in expr is an ast.Type
|
|
// Use it to generate `it` variable.
|
|
first_expr := branch.exprs[0]
|
|
match first_expr {
|
|
ast.Type {
|
|
it_type := g.typ(it.typ)
|
|
// g.writeln('$it_type* it = ($it_type*)${tmp}.obj; // ST it')
|
|
g.write('\t$it_type* it = ($it_type*)')
|
|
g.expr(node.cond)
|
|
g.writeln('.obj; // ST it')
|
|
}
|
|
else {
|
|
verror('match sum type')
|
|
}
|
|
}
|
|
}
|
|
g.stmts(branch.stmts)
|
|
if !g.inside_ternary {
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
g.inside_ternary = false
|
|
}
|
|
|
|
fn (g mut Gen) ident(node ast.Ident) {
|
|
if node.name == 'lld' {
|
|
return
|
|
}
|
|
if node.name.starts_with('C.') {
|
|
g.write(node.name[2..].replace('.', '__'))
|
|
return
|
|
}
|
|
if node.kind == .constant && !node.name.starts_with('g_') {
|
|
// TODO globals hack
|
|
g.write('_const_')
|
|
}
|
|
name := c_name(node.name)
|
|
// TODO `is`
|
|
match node.info {
|
|
ast.IdentVar {
|
|
// x ?int
|
|
// `x = 10` => `x.data = 10` (g.right_is_opt == false)
|
|
// `x = new_opt()` => `x = new_opt()` (g.right_is_opt == true)
|
|
// `println(x)` => `println(*(int*)x.data)`
|
|
if it.is_optional && !(g.is_assign_lhs && g.right_is_opt) {
|
|
g.write('/*opt*/')
|
|
styp := g.typ(it.typ)[7..] // Option_int => int TODO perf?
|
|
g.write('(*($styp*)${name}.data)')
|
|
return
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
g.write(name)
|
|
}
|
|
|
|
fn (g mut Gen) if_expr(node ast.IfExpr) {
|
|
// println('if_expr pos=$node.pos.line_nr')
|
|
// g.writeln('/* if is_expr=$node.is_expr */')
|
|
// If expression? Assign the value to a temp var.
|
|
// Previously ?: was used, but it's too unreliable.
|
|
type_sym := g.table.get_type_symbol(node.typ)
|
|
mut tmp := ''
|
|
if type_sym.kind != .void {
|
|
tmp = g.new_tmp_var()
|
|
// g.writeln('$ti.name $tmp;')
|
|
}
|
|
// one line ?:
|
|
// TODO clean this up once `is` is supported
|
|
// TODO: make sure only one stmt in each branch
|
|
if node.is_expr && node.branches.len >= 2 && node.has_else && type_sym.kind != .void {
|
|
g.inside_ternary = true
|
|
g.write('(')
|
|
for i, branch in node.branches {
|
|
if i > 0 {
|
|
g.write(' : ')
|
|
}
|
|
if i < node.branches.len - 1 || !node.has_else {
|
|
g.expr(branch.cond)
|
|
g.write(' ? ')
|
|
}
|
|
g.stmts(branch.stmts)
|
|
}
|
|
g.write(')')
|
|
g.inside_ternary = false
|
|
} else {
|
|
guard_ok := g.new_tmp_var()
|
|
mut is_guard := false
|
|
for i, branch in node.branches {
|
|
if i == 0 {
|
|
match branch.cond {
|
|
ast.IfGuardExpr {
|
|
is_guard = true
|
|
g.writeln('bool $guard_ok;')
|
|
g.write('{ /* if guard */ ${g.typ(it.expr_type)} $it.var_name = ')
|
|
g.expr(it.expr)
|
|
g.writeln(';')
|
|
g.writeln('if (($guard_ok = ${it.var_name}.ok)) {')
|
|
}
|
|
else {
|
|
g.write('if (')
|
|
g.expr(branch.cond)
|
|
g.writeln(') {')
|
|
}
|
|
}
|
|
} else if i < node.branches.len - 1 || !node.has_else {
|
|
g.write('} else if (')
|
|
g.expr(branch.cond)
|
|
g.writeln(') {')
|
|
} else if i == node.branches.len - 1 && node.has_else {
|
|
if is_guard {
|
|
g.writeln('} if (!$guard_ok) { /* else */')
|
|
} else {
|
|
g.writeln('} else {')
|
|
}
|
|
}
|
|
// Assign ret value
|
|
// if i == node.stmts.len - 1 && type_sym.kind != .void {}
|
|
// g.writeln('$tmp =')
|
|
g.stmts(branch.stmts)
|
|
}
|
|
if is_guard {
|
|
g.write('}')
|
|
}
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) index_expr(node ast.IndexExpr) {
|
|
// TODO else doesn't work with sum types
|
|
mut is_range := false
|
|
match node.index {
|
|
ast.RangeExpr {
|
|
sym := g.table.get_type_symbol(node.left_type)
|
|
is_range = true
|
|
if sym.kind == .string {
|
|
g.write('string_substr(')
|
|
g.expr(node.left)
|
|
} else if sym.kind == .array {
|
|
g.write('array_slice(')
|
|
g.expr(node.left)
|
|
} else if sym.kind == .array_fixed {
|
|
// Convert a fixed array to V array when doing `fixed_arr[start..end]`
|
|
g.write('array_slice(new_array_from_c_array(_ARR_LEN(')
|
|
g.expr(node.left)
|
|
g.write('), _ARR_LEN(')
|
|
g.expr(node.left)
|
|
g.write('), sizeof(')
|
|
g.expr(node.left)
|
|
g.write('[0]), ')
|
|
g.expr(node.left)
|
|
g.write(')')
|
|
} else {
|
|
g.expr(node.left)
|
|
}
|
|
g.write(', ')
|
|
if it.has_low {
|
|
g.expr(it.low)
|
|
} else {
|
|
g.write('0')
|
|
}
|
|
g.write(', ')
|
|
if it.has_high {
|
|
g.expr(it.high)
|
|
} else {
|
|
g.expr(node.left)
|
|
g.write('.len')
|
|
}
|
|
g.write(')')
|
|
return
|
|
}
|
|
else {}
|
|
}
|
|
if !is_range {
|
|
sym := g.table.get_type_symbol(node.left_type)
|
|
left_is_ptr := table.type_is_ptr(node.left_type)
|
|
if table.type_is(node.left_type, .variadic) {
|
|
g.expr(node.left)
|
|
g.write('.args')
|
|
g.write('[')
|
|
g.expr(node.index)
|
|
g.write(']')
|
|
} else if sym.kind == .array {
|
|
info := sym.info as table.Array
|
|
elem_type_str := g.typ(info.elem_type)
|
|
// `vals[i].field = x` is an exception and requires `array_get`:
|
|
// `(*(Val*)array_get(vals, i)).field = x ;`
|
|
mut is_selector := false
|
|
match node.left {
|
|
ast.SelectorExpr {
|
|
is_selector = true
|
|
}
|
|
else {}
|
|
}
|
|
if g.is_assign_lhs && !is_selector && node.is_setter {
|
|
g.is_array_set = true
|
|
g.write('array_set(')
|
|
if !left_is_ptr {
|
|
g.write('&')
|
|
}
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write(', &($elem_type_str[]) { ')
|
|
// `x[0] *= y`
|
|
if g.assign_op in [.mult_assign] {
|
|
g.write('*($elem_type_str*)array_get(')
|
|
if left_is_ptr {
|
|
g.write('*')
|
|
}
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write(') ')
|
|
op := match g.assign_op {
|
|
.mult_assign {
|
|
'*'
|
|
}
|
|
else {
|
|
''
|
|
}
|
|
}
|
|
g.write(op)
|
|
}
|
|
} else {
|
|
g.write('(*($elem_type_str*)array_get(')
|
|
if left_is_ptr {
|
|
g.write('*')
|
|
}
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write('))')
|
|
}
|
|
} else if sym.kind == .map {
|
|
info := sym.info as table.Map
|
|
elem_type_str := g.typ(info.value_type)
|
|
if g.is_assign_lhs {
|
|
g.is_array_set = true
|
|
g.write('map_set(')
|
|
if !left_is_ptr {
|
|
g.write('&')
|
|
}
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write(', &($elem_type_str[]) { ')
|
|
} else {
|
|
/*
|
|
g.write('(*($elem_type_str*)map_get2(')
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write('))')
|
|
*/
|
|
zero := g.type_default(info.value_type)
|
|
g.write('(*($elem_type_str*)map_get3(')
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write(', &($elem_type_str[]){ $zero }))')
|
|
}
|
|
} else if sym.kind == .string && !table.type_is_ptr(node.left_type) {
|
|
g.write('string_at(')
|
|
g.expr(node.left)
|
|
g.write(', ')
|
|
g.expr(node.index)
|
|
g.write(')')
|
|
} else {
|
|
g.expr(node.left)
|
|
g.write('[')
|
|
g.expr(node.index)
|
|
g.write(']')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) return_statement(node ast.Return) {
|
|
g.write('return')
|
|
if g.fn_decl.name == 'main' {
|
|
g.writeln(' 0;')
|
|
return
|
|
}
|
|
fn_return_is_optional := table.type_is(g.fn_decl.return_type, .optional)
|
|
// multiple returns
|
|
if node.exprs.len > 1 {
|
|
g.write(' ')
|
|
typ_sym := g.table.get_type_symbol(g.fn_decl.return_type)
|
|
mr_info := typ_sym.info as table.MultiReturn
|
|
mut styp := g.typ(g.fn_decl.return_type)
|
|
if fn_return_is_optional { // && !table.type_is(node.types[0], .optional) && node.types[0] !=
|
|
styp = styp[7..] // remove 'Option_'
|
|
g.write('opt_ok(& ($styp []) { ')
|
|
}
|
|
g.write('($styp){')
|
|
for i, expr in node.exprs {
|
|
g.write('.arg$i=')
|
|
g.expr(expr)
|
|
if i < node.exprs.len - 1 {
|
|
g.write(',')
|
|
}
|
|
}
|
|
g.write('}')
|
|
if fn_return_is_optional {
|
|
g.write(' }, sizeof($styp))')
|
|
}
|
|
} else if node.exprs.len == 1 {
|
|
// normal return
|
|
g.write(' ')
|
|
return_sym := g.table.get_type_symbol(node.types[0])
|
|
// `return opt_ok(expr)` for functions that expect an optional
|
|
if fn_return_is_optional && !table.type_is(node.types[0], .optional) && return_sym.name !=
|
|
'Option' {
|
|
mut is_none := false
|
|
mut is_error := false
|
|
expr0 := node.exprs[0]
|
|
match expr0 {
|
|
ast.None {
|
|
is_none = true
|
|
}
|
|
ast.CallExpr {
|
|
// TODO: why?
|
|
// if !it.is_method {
|
|
if it.name == 'error' {
|
|
is_error = true // TODO check name 'error'
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
if !is_none && !is_error {
|
|
styp := g.typ(g.fn_decl.return_type)[7..] // remove 'Option_'
|
|
g.write('/*:)$return_sym.name*/opt_ok(&($styp []) { ')
|
|
g.expr(node.exprs[0])
|
|
g.writeln(' }, sizeof($styp));')
|
|
return
|
|
}
|
|
// g.write('/*OPTIONAL*/')
|
|
}
|
|
if !table.type_is_ptr(g.fn_decl.return_type) && table.type_is_ptr(node.types[0]) {
|
|
// Automatic Dereference
|
|
g.write('*')
|
|
}
|
|
g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type)
|
|
}
|
|
g.writeln(';')
|
|
}
|
|
|
|
fn (g mut Gen) const_decl(node ast.ConstDecl) {
|
|
for i, field in node.fields {
|
|
name := c_name(field.name)
|
|
// TODO hack. Cut the generated value and paste it into definitions.
|
|
pos := g.out.len
|
|
g.expr(field.expr)
|
|
val := g.out.after(pos)
|
|
g.out.go_back(val.len)
|
|
match field.expr {
|
|
ast.CharLiteral {
|
|
g.const_decl_simple_define(name, val)
|
|
}
|
|
ast.IntegerLiteral {
|
|
g.const_decl_simple_define(name, val)
|
|
}
|
|
ast.StringLiteral {
|
|
g.definitions.writeln('string _const_$name; // a string literal, inited later')
|
|
if g.pref.build_mode != .build_module {
|
|
g.stringliterals.writeln('\t_const_$name = $val;')
|
|
}
|
|
}
|
|
else {
|
|
// Initialize more complex consts in `void _vinit(){}`
|
|
// (C doesn't allow init expressions that can't be resolved at compile time).
|
|
styp := g.typ(field.typ)
|
|
g.definitions.writeln('$styp _const_$name; // inited later')
|
|
g.inits.writeln('\t_const_$name = $val;')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) const_decl_simple_define(name, val string) {
|
|
// Simple expressions should use a #define
|
|
// so that we don't pollute the binary with unnecessary global vars
|
|
// Do not do this when building a module, otherwise the consts
|
|
// will not be accessible.
|
|
g.definitions.write('#define _const_$name ')
|
|
g.definitions.writeln(val)
|
|
}
|
|
|
|
fn (g mut Gen) struct_init(it ast.StructInit) {
|
|
mut info := table.Struct{}
|
|
mut is_struct := false
|
|
sym := g.table.get_type_symbol(it.typ)
|
|
if sym.kind == .struct_ {
|
|
is_struct = true
|
|
info = sym.info as table.Struct
|
|
}
|
|
// info := g.table.get_type_symbol(it.typ).info as table.Struct
|
|
// println(info.fields.len)
|
|
styp := g.typ(it.typ)
|
|
is_amp := g.is_amp
|
|
if is_amp {
|
|
g.out.go_back(1) // delete the & already generated in `prefix_expr()
|
|
g.write('($styp*)memdup(&($styp){')
|
|
} else {
|
|
g.writeln('($styp){')
|
|
}
|
|
mut fields := []string
|
|
mut inited_fields := []string // TODO this is done in checker, move to ast node
|
|
if it.fields.len == 0 && it.exprs.len > 0 {
|
|
// Get fields for {a,b} short syntax. Fields array wasn't set in the parser.
|
|
for f in info.fields {
|
|
fields << f.name
|
|
}
|
|
} else {
|
|
fields = it.fields
|
|
}
|
|
// / User set fields
|
|
for i, field in fields {
|
|
field_name := c_name(field)
|
|
inited_fields << field
|
|
g.write('\t.$field_name = ')
|
|
g.expr_with_cast(it.exprs[i], it.expr_types[i], it.expected_types[i])
|
|
g.writeln(',')
|
|
}
|
|
// The rest of the fields are zeroed.
|
|
if is_struct {
|
|
for field in info.fields {
|
|
if field.name in inited_fields {
|
|
continue
|
|
}
|
|
if table.type_is(field.typ, .optional) {
|
|
// TODO handle/require optionals in inits
|
|
continue
|
|
}
|
|
field_name := c_name(field.name)
|
|
zero := if field.default_val != '' { field.default_val } else { g.type_default(field.typ) }
|
|
g.writeln('\t.$field_name = $zero,') // zer0')
|
|
}
|
|
}
|
|
if it.fields.len == 0 && info.fields.len == 0 {
|
|
g.write('0')
|
|
}
|
|
g.write('}')
|
|
if is_amp {
|
|
g.write(', sizeof($styp))')
|
|
}
|
|
}
|
|
|
|
// { user | name: 'new name' }
|
|
fn (g mut Gen) assoc(node ast.Assoc) {
|
|
g.writeln('// assoc')
|
|
if node.typ == 0 {
|
|
return
|
|
}
|
|
styp := g.typ(node.typ)
|
|
g.writeln('($styp){')
|
|
for i, field in node.fields {
|
|
field_name := c_name(field)
|
|
g.write('\t.$field_name = ')
|
|
g.expr(node.exprs[i])
|
|
g.writeln(', ')
|
|
}
|
|
// Copy the rest of the fields.
|
|
sym := g.table.get_type_symbol(node.typ)
|
|
info := sym.info as table.Struct
|
|
for field in info.fields {
|
|
if field.name in node.fields {
|
|
continue
|
|
}
|
|
field_name := c_name(field.name)
|
|
g.writeln('\t.$field_name = ${node.var_name}.$field_name,')
|
|
}
|
|
g.write('}')
|
|
if g.is_amp {
|
|
g.write(', sizeof($styp))')
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) call_args(args []ast.CallArg, expected_types []table.Type) {
|
|
is_variadic := expected_types.len > 0 && table.type_is(expected_types[expected_types.len -
|
|
1], .variadic)
|
|
mut arg_no := 0
|
|
for arg in args {
|
|
if is_variadic && arg_no == expected_types.len - 1 {
|
|
break
|
|
}
|
|
// some c fn definitions dont have args (cfns.v) or are not updated in checker
|
|
// when these are fixed we wont need this check
|
|
if arg_no < expected_types.len {
|
|
g.ref_or_deref_arg(arg, expected_types[arg_no])
|
|
} else {
|
|
g.expr(arg.expr)
|
|
}
|
|
if arg_no < args.len - 1 || is_variadic {
|
|
g.write(', ')
|
|
}
|
|
arg_no++
|
|
}
|
|
if is_variadic {
|
|
varg_type := expected_types[expected_types.len - 1]
|
|
struct_name := 'varg_' + g.typ(varg_type).replace('*', '_ptr')
|
|
variadic_count := args.len - arg_no
|
|
varg_type_str := int(varg_type).str()
|
|
if variadic_count > g.variadic_args[varg_type_str] {
|
|
g.variadic_args[varg_type_str] = variadic_count
|
|
}
|
|
g.write('($struct_name){.len=$variadic_count,.args={')
|
|
if variadic_count > 0 {
|
|
for j in arg_no .. args.len {
|
|
g.ref_or_deref_arg(args[j], varg_type)
|
|
if j < args.len - 1 {
|
|
g.write(', ')
|
|
}
|
|
}
|
|
} else {
|
|
g.write('0')
|
|
}
|
|
g.write('}}')
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) generate_array_equality_fn(ptr_typ string, styp table.Type, sym &table.TypeSymbol) {
|
|
g.array_fn_definitions << ptr_typ
|
|
g.definitions.writeln('bool ${ptr_typ}_arr_eq(array_${ptr_typ} a, array_${ptr_typ} b) {')
|
|
g.definitions.writeln('\tif (a.len != b.len) {')
|
|
g.definitions.writeln('\t\treturn false;')
|
|
g.definitions.writeln('\t}')
|
|
g.definitions.writeln('\tfor (int i = 0; i < a.len; i++) {')
|
|
if styp == table.string_type_idx {
|
|
g.definitions.writeln('\t\tif (string_ne(*((${ptr_typ}*)(a.data+(i*a.element_size))), *((${ptr_typ}*)(b.data+(i*b.element_size))))) {')
|
|
} else if sym.kind == .struct_ {
|
|
g.definitions.writeln('\t\tif (memcmp((void*)(a.data+(i*a.element_size)), (void*)(b.data+(i*b.element_size)), a.element_size)) {')
|
|
} else {
|
|
g.definitions.writeln('\t\tif (*((${ptr_typ}*)(a.data+(i*a.element_size))) != *((${ptr_typ}*)(b.data+(i*b.element_size)))) {')
|
|
}
|
|
g.definitions.writeln('\t\t\treturn false;')
|
|
g.definitions.writeln('\t\t}')
|
|
g.definitions.writeln('\t}')
|
|
g.definitions.writeln('\treturn true;')
|
|
g.definitions.writeln('}')
|
|
}
|
|
|
|
[inline]
|
|
fn (g mut Gen) ref_or_deref_arg(arg ast.CallArg, expected_type table.Type) {
|
|
arg_is_ptr := table.type_is_ptr(expected_type) || table.type_idx(expected_type) in table.pointer_type_idxs
|
|
expr_is_ptr := table.type_is_ptr(arg.typ) || table.type_idx(arg.typ) in table.pointer_type_idxs
|
|
if arg.is_mut && !arg_is_ptr {
|
|
g.write('&/*mut*/')
|
|
} else if arg_is_ptr && !expr_is_ptr {
|
|
if arg.is_mut {
|
|
sym := g.table.get_type_symbol(expected_type)
|
|
if sym.kind == .array {
|
|
// Special case for mutable arrays. We can't `&` function
|
|
// results, have to use `(array[]){ expr }[0]` hack.
|
|
g.write('&/*111*/(array[]){')
|
|
g.expr(arg.expr)
|
|
g.write('}[0]')
|
|
return
|
|
}
|
|
}
|
|
g.write('&/*qq*/')
|
|
} else if !arg_is_ptr && expr_is_ptr {
|
|
// Dereference a pointer if a value is required
|
|
g.write('*/*d*/')
|
|
}
|
|
g.expr_with_cast(arg.expr, arg.typ, expected_type)
|
|
}
|
|
|
|
fn verror(s string) {
|
|
util.verror('cgen error', s)
|
|
}
|
|
|
|
fn (g mut Gen) write_init_function() {
|
|
g.writeln('void _vinit() {')
|
|
g.writeln('\tbuiltin_init();')
|
|
g.writeln('\tvinit_string_literals();')
|
|
g.writeln(g.inits.str())
|
|
g.writeln('}')
|
|
if g.autofree {
|
|
g.writeln('void _vcleanup() {')
|
|
// g.writeln('puts("cleaning up...");')
|
|
if g.is_importing_os() {
|
|
g.writeln('free(_const_os__args.data);')
|
|
g.writeln('string_free(_const_os__wd_at_startup);')
|
|
}
|
|
g.writeln('free(_const_strconv__ftoa__powers_of_10.data);')
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) write_str_fn_definitions() {
|
|
// _STR function can't be defined in vlib
|
|
g.writeln('
|
|
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);
|
|
} // endof _STR_TMP
|
|
|
|
')
|
|
}
|
|
|
|
const (
|
|
builtins = ['string', 'array', 'KeyValue', 'DenseArray', 'map', 'Option']
|
|
)
|
|
|
|
fn (g mut Gen) write_builtin_types() {
|
|
mut builtin_types := []table.TypeSymbol // builtin types
|
|
// builtin types need to be on top
|
|
// everything except builtin will get sorted
|
|
for builtin_name in builtins {
|
|
builtin_types << g.table.types[g.table.type_idxs[builtin_name]]
|
|
}
|
|
g.write_types(builtin_types)
|
|
}
|
|
|
|
// C struct definitions, ordered
|
|
// Sort the types, make sure types that are referenced by other types
|
|
// are added before them.
|
|
fn (g mut Gen) write_sorted_types() {
|
|
mut types := []table.TypeSymbol // structs that need to be sorted
|
|
for typ in g.table.types {
|
|
if !(typ.name in builtins) {
|
|
types << typ
|
|
}
|
|
}
|
|
// sort structs
|
|
types_sorted := g.sort_structs(types)
|
|
// Generate C code
|
|
g.definitions.writeln('// builtin types:')
|
|
g.definitions.writeln('//------------------ #endbuiltin')
|
|
g.write_types(types_sorted)
|
|
}
|
|
|
|
fn (g mut Gen) write_types(types []table.TypeSymbol) {
|
|
for typ in types {
|
|
if typ.name.starts_with('C.') {
|
|
continue
|
|
}
|
|
// sym := g.table.get_type_symbol(typ)
|
|
name := typ.name.replace('.', '__')
|
|
match typ.info {
|
|
table.Struct {
|
|
info := typ.info as table.Struct
|
|
// g.definitions.writeln('typedef struct {')
|
|
if info.is_union {
|
|
g.definitions.writeln('union $name {')
|
|
} else {
|
|
g.definitions.writeln('struct $name {')
|
|
}
|
|
if info.fields.len > 0 {
|
|
for field in info.fields {
|
|
type_name := g.typ(field.typ)
|
|
field_name := c_name(field.name)
|
|
g.definitions.writeln('\t$type_name $field_name;')
|
|
}
|
|
} else {
|
|
g.definitions.writeln('EMPTY_STRUCT_DECLARATION;')
|
|
}
|
|
// g.definitions.writeln('} $name;\n')
|
|
//
|
|
g.definitions.writeln('};\n')
|
|
}
|
|
table.Alias {
|
|
// table.Alias, table.SumType { TODO
|
|
}
|
|
table.SumType {
|
|
g.definitions.writeln('// Sum type')
|
|
g.definitions.writeln('
|
|
typedef struct {
|
|
void* obj;
|
|
int typ;
|
|
} $name;')
|
|
}
|
|
table.ArrayFixed {
|
|
// .array_fixed {
|
|
styp := typ.name.replace('.', '__')
|
|
// array_fixed_char_300 => char x[300]
|
|
mut fixed := styp[12..]
|
|
len := styp.after('_')
|
|
fixed = fixed[..fixed.len - len.len - 1]
|
|
g.definitions.writeln('typedef $fixed $styp [$len];')
|
|
// }
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort structs by dependant fields
|
|
fn (g Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol {
|
|
mut dep_graph := depgraph.new_dep_graph()
|
|
// types name list
|
|
mut type_names := []string
|
|
for typ in typesa {
|
|
type_names << typ.name
|
|
}
|
|
// loop over types
|
|
for t in typesa {
|
|
// create list of deps
|
|
mut field_deps := []string
|
|
match t.info {
|
|
table.ArrayFixed {
|
|
dep := g.table.get_type_symbol(it.elem_type).name
|
|
if dep in type_names {
|
|
field_deps << dep
|
|
}
|
|
}
|
|
table.Struct {
|
|
info := t.info as table.Struct
|
|
for field in info.fields {
|
|
dep := g.table.get_type_symbol(field.typ).name
|
|
// skip if not in types list or already in deps
|
|
if !(dep in type_names) || dep in field_deps || table.type_is_ptr(field.typ) {
|
|
continue
|
|
}
|
|
field_deps << dep
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
// 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 := []table.TypeSymbol
|
|
for node in dep_graph_sorted.nodes {
|
|
types_sorted << g.table.types[g.table.type_idxs[node.name]]
|
|
}
|
|
return types_sorted
|
|
}
|
|
|
|
fn (g mut Gen) string_inter_literal(node ast.StringInterLiteral) {
|
|
g.write('_STR("')
|
|
// Build the string with %
|
|
for i, val in node.vals {
|
|
escaped_val := val.replace_each(['"', '\\"', '\r\n', '\\n', '\n', '\\n'])
|
|
g.write(escaped_val)
|
|
if i >= node.exprs.len {
|
|
continue
|
|
}
|
|
// TODO: fix match, sum type false positive
|
|
// match node.expr_types[i] {
|
|
// table.string_type {
|
|
// g.write('%.*s')
|
|
// }
|
|
// table.int_type {
|
|
// g.write('%d')
|
|
// }
|
|
// else {}
|
|
// }
|
|
sym := g.table.get_type_symbol(node.expr_types[i])
|
|
sfmt := node.expr_fmts[i]
|
|
if sfmt.len > 0 {
|
|
fspec := sfmt[sfmt.len - 1]
|
|
if fspec == `s` && node.expr_types[i] != table.string_type {
|
|
verror('only V strings can be formatted with a ${sfmt} format')
|
|
}
|
|
g.write('%' + sfmt[1..])
|
|
} else if node.expr_types[i] in [table.string_type, table.bool_type] || sym.kind ==
|
|
.enum_ {
|
|
g.write('%.*s')
|
|
} else {
|
|
match node.exprs[i] {
|
|
ast.EnumVal {
|
|
g.write('%.*s')
|
|
}
|
|
else {
|
|
g.write('%d')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
g.write('", ')
|
|
// Build args
|
|
for i, expr in node.exprs {
|
|
sfmt := node.expr_fmts[i]
|
|
if sfmt.len > 0 {
|
|
fspec := sfmt[sfmt.len - 1]
|
|
if fspec == `s` && node.expr_types[i] == table.string_type {
|
|
g.expr(expr)
|
|
g.write('.str')
|
|
} else {
|
|
g.expr(expr)
|
|
}
|
|
} else if node.expr_types[i] == table.string_type {
|
|
// `name.str, name.len,`
|
|
g.expr(expr)
|
|
g.write('.len, ')
|
|
g.expr(expr)
|
|
g.write('.str')
|
|
} else if node.expr_types[i] == table.bool_type {
|
|
g.expr(expr)
|
|
g.write(' ? 4 : 5, ')
|
|
g.expr(expr)
|
|
g.write(' ? "true" : "false"')
|
|
} else {
|
|
sym := g.table.get_type_symbol(node.expr_types[i])
|
|
if sym.kind == .enum_ {
|
|
is_var := match node.exprs[i] {
|
|
ast.SelectorExpr {
|
|
true
|
|
}
|
|
ast.Ident {
|
|
true
|
|
}
|
|
else {
|
|
false
|
|
}
|
|
}
|
|
if is_var {
|
|
styp := g.typ(node.expr_types[i])
|
|
g.gen_str_for_type(sym, styp)
|
|
g.write('${styp}_str(')
|
|
g.enum_expr(expr)
|
|
g.write(')')
|
|
g.write('.len, ')
|
|
g.write('${styp}_str(')
|
|
g.enum_expr(expr)
|
|
g.write(').str')
|
|
} else {
|
|
g.write('tos3("')
|
|
g.enum_expr(expr)
|
|
g.write('")')
|
|
g.write('.len, ')
|
|
g.write('"')
|
|
g.enum_expr(expr)
|
|
g.write('"')
|
|
}
|
|
} else {
|
|
g.expr(expr)
|
|
}
|
|
}
|
|
if i < node.exprs.len - 1 {
|
|
g.write(', ')
|
|
}
|
|
}
|
|
g.write(')')
|
|
}
|
|
|
|
// `nums.filter(it % 2 == 0)`
|
|
fn (g mut Gen) gen_filter(node ast.CallExpr) {
|
|
tmp := g.new_tmp_var()
|
|
s := g.out.after(g.stmt_start_pos) // the already generated part of current statement
|
|
g.out.go_back(s.len)
|
|
// println('filter s="$s"')
|
|
sym := g.table.get_type_symbol(node.return_type)
|
|
if sym.kind != .array {
|
|
verror('filter() requires an array')
|
|
}
|
|
info := sym.info as table.Array
|
|
styp := g.typ(node.return_type)
|
|
elem_type_str := g.typ(info.elem_type)
|
|
g.write('\nint ${tmp}_len = ')
|
|
g.expr(node.left)
|
|
g.writeln('.len;')
|
|
g.writeln('$styp $tmp = new_array(0, ${tmp}_len, sizeof($elem_type_str));')
|
|
g.writeln('for (int i = 0; i < ${tmp}_len; i++) {')
|
|
g.write(' $elem_type_str it = (($elem_type_str*) ')
|
|
g.expr(node.left)
|
|
g.writeln('.data)[i];')
|
|
g.write('if (')
|
|
g.expr(node.args[0].expr) // the first arg is the filter condition
|
|
g.writeln(') array_push(&$tmp, &it); \n }')
|
|
g.write(s)
|
|
g.write(' ')
|
|
g.write(tmp)
|
|
}
|
|
|
|
fn (g mut Gen) insert_before(s string) {
|
|
cur_line := g.out.after(g.stmt_start_pos)
|
|
g.out.go_back(cur_line.len)
|
|
g.writeln(s)
|
|
g.write(cur_line)
|
|
}
|
|
|
|
fn (g mut Gen) call_expr(node ast.CallExpr) {
|
|
gen_or := !g.is_assign_rhs && node.or_block.stmts.len > 0
|
|
tmp_opt := if gen_or { g.new_tmp_var() } else { '' }
|
|
if gen_or {
|
|
styp := g.typ(node.return_type)
|
|
g.write('$styp $tmp_opt = ')
|
|
}
|
|
if node.is_method {
|
|
g.method_call(node)
|
|
} else {
|
|
g.fn_call(node)
|
|
}
|
|
if gen_or {
|
|
g.or_block(tmp_opt, node.or_block.stmts, node.return_type)
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) method_call(node ast.CallExpr) {
|
|
// TODO: there are still due to unchecked exprs (opt/some fn arg)
|
|
if node.left_type == 0 {
|
|
verror('method receiver type is 0, this means there are some uchecked exprs')
|
|
}
|
|
typ_sym := g.table.get_type_symbol(node.receiver_type)
|
|
// rec_sym := g.table.get_type_symbol(node.receiver_type)
|
|
mut receiver_name := typ_sym.name
|
|
if typ_sym.kind == .array && node.name == 'filter' {
|
|
g.gen_filter(node)
|
|
return
|
|
}
|
|
// TODO performance, detect `array` method differently
|
|
if typ_sym.kind == .array && node.name in ['repeat', 'sort_with_compare', 'free', 'push_many',
|
|
'trim', 'first', 'last', 'clone', 'reverse', 'slice'] {
|
|
// && rec_sym.name == 'array' {
|
|
// && rec_sym.name == 'array' && receiver_name.starts_with('array') {
|
|
// `array_byte_clone` => `array_clone`
|
|
receiver_name = 'array'
|
|
if node.name in ['last', 'first'] {
|
|
return_type_str := g.typ(node.return_type)
|
|
g.write('*($return_type_str*)')
|
|
}
|
|
}
|
|
name := '${receiver_name}_$node.name'.replace('.', '__')
|
|
// if node.receiver_type != 0 {
|
|
// g.write('/*${g.typ(node.receiver_type)}*/')
|
|
// g.write('/*expr_type=${g.typ(node.left_type)} rec type=${g.typ(node.receiver_type)}*/')
|
|
// }
|
|
g.write('${name}(')
|
|
if table.type_is_ptr(node.receiver_type) && !table.type_is_ptr(node.left_type) {
|
|
// The receiver is a reference, but the caller provided a value
|
|
// Add `&` automatically.
|
|
// TODO same logic in call_args()
|
|
g.write('&')
|
|
} else if !table.type_is_ptr(node.receiver_type) && table.type_is_ptr(node.left_type) {
|
|
g.write('/*rec*/*')
|
|
}
|
|
g.expr(node.left)
|
|
is_variadic := node.expected_arg_types.len > 0 && table.type_is(node.expected_arg_types[node.expected_arg_types.len -
|
|
1], .variadic)
|
|
if node.args.len > 0 || is_variadic {
|
|
g.write(', ')
|
|
}
|
|
// /////////
|
|
/*
|
|
if name.contains('subkeys') {
|
|
println('call_args $name $node.arg_types.len')
|
|
for t in node.arg_types {
|
|
sym := g.table.get_type_symbol(t)
|
|
print('$sym.name ')
|
|
}
|
|
println('')
|
|
}
|
|
*/
|
|
// ///////
|
|
g.call_args(node.args, node.expected_arg_types)
|
|
g.write(')')
|
|
// if node.or_block.stmts.len > 0 {
|
|
// g.or_block(node.or_block.stmts, node.return_type)
|
|
// }
|
|
}
|
|
|
|
fn (g mut Gen) fn_call(node ast.CallExpr) {
|
|
mut name := node.name
|
|
is_print := name == 'println' || name == 'print'
|
|
print_method := if name == 'println' { 'println' } else { 'print' }
|
|
if node.is_c {
|
|
// Skip "C."
|
|
g.is_c_call = true
|
|
name = name[2..].replace('.', '__')
|
|
} else {
|
|
name = c_name(name)
|
|
}
|
|
// Generate tmp vars for values that have to be freed.
|
|
/*
|
|
mut tmps := []string
|
|
for arg in node.args {
|
|
if arg.typ == table.string_type_idx || is_print {
|
|
tmp := g.new_tmp_var()
|
|
tmps << tmp
|
|
g.write('string $tmp = ')
|
|
g.expr(arg.expr)
|
|
g.writeln('; //memory')
|
|
}
|
|
}
|
|
*/
|
|
if is_print && node.args[0].typ != table.string_type {
|
|
typ := node.args[0].typ
|
|
mut styp := g.typ(typ)
|
|
sym := g.table.get_type_symbol(typ)
|
|
if table.type_is_ptr(typ) {
|
|
styp = styp.replace('*', '')
|
|
}
|
|
g.gen_str_for_type(sym, styp)
|
|
if g.autofree && !table.type_is(typ, .optional) {
|
|
// Create a temporary variable so that the value can be freed
|
|
tmp := g.new_tmp_var()
|
|
// tmps << tmp
|
|
g.write('string $tmp = ${styp}_str(')
|
|
g.expr(node.args[0].expr)
|
|
g.writeln('); ${print_method}($tmp); string_free($tmp); //MEM2 $styp')
|
|
} else {
|
|
expr := node.args[0].expr
|
|
is_var := match expr {
|
|
ast.SelectorExpr {
|
|
true
|
|
}
|
|
ast.Ident {
|
|
true
|
|
}
|
|
else {
|
|
false
|
|
}
|
|
}
|
|
if table.type_is_ptr(typ) && sym.kind != .struct_ {
|
|
// ptr_str() for pointers
|
|
styp = 'ptr'
|
|
}
|
|
if sym.kind == .enum_ {
|
|
if is_var {
|
|
g.write('${print_method}(${styp}_str(')
|
|
} else {
|
|
// when no var, print string directly
|
|
g.write('${print_method}(tos3("')
|
|
}
|
|
if table.type_is_ptr(typ) {
|
|
// dereference
|
|
g.write('*')
|
|
}
|
|
g.enum_expr(expr)
|
|
if !is_var {
|
|
// end of string
|
|
g.write('"')
|
|
}
|
|
} else {
|
|
g.write('${print_method}(${styp}_str(')
|
|
if table.type_is_ptr(typ) && sym.kind == .struct_ {
|
|
// dereference
|
|
g.write('*')
|
|
}
|
|
g.expr(expr)
|
|
if sym.kind == .struct_ && styp != 'ptr' && !sym.has_method('str') {
|
|
g.write(', 0') // trailing 0 is initial struct indent count
|
|
}
|
|
}
|
|
g.write('))')
|
|
}
|
|
} else {
|
|
g.write('${name}(')
|
|
g.call_args(node.args, node.expected_arg_types)
|
|
g.write(')')
|
|
}
|
|
// if node.or_block.stmts.len > 0 {
|
|
// g.or_block(node.or_block.stmts, node.return_type)
|
|
// }
|
|
g.is_c_call = false
|
|
}
|
|
|
|
// If user is accessing the return value eg. in assigment, pass the variable name.
|
|
// If the user is not using the optional return value. We need to pass a temp var
|
|
// to access its fields (`.ok`, `.error` etc)
|
|
// `os.cp(...)` => `Option bool tmp = os__cp(...); if (!tmp.ok) { ... }`
|
|
fn (g mut Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Type) {
|
|
mr_styp := g.typ(return_type)
|
|
mr_styp2 := mr_styp[7..] // remove Option_
|
|
g.writeln(';') // or')
|
|
g.writeln('if (!${var_name}.ok) {')
|
|
g.writeln('\tstring err = ${var_name}.v_error;')
|
|
g.writeln('\tint errcode = ${var_name}.ecode;')
|
|
last_type, type_of_last_expression := g.type_of_last_statement(stmts)
|
|
if last_type == 'v.ast.ExprStmt' && type_of_last_expression != 'void' {
|
|
g.indent++
|
|
for i, stmt in stmts {
|
|
if i == stmts.len - 1 {
|
|
g.indent--
|
|
g.write('\t*(${mr_styp2}*) ${var_name}.data = ')
|
|
}
|
|
g.stmt(stmt)
|
|
}
|
|
} else {
|
|
g.stmts(stmts)
|
|
}
|
|
g.write('}')
|
|
}
|
|
|
|
fn (g mut Gen) type_of_last_statement(stmts []ast.Stmt) (string, string) {
|
|
mut last_type := ''
|
|
mut last_expr_result_type := ''
|
|
if stmts.len > 0 {
|
|
last_stmt := stmts[stmts.len - 1]
|
|
last_type = typeof(last_stmt)
|
|
if last_type == 'v.ast.ExprStmt' {
|
|
match last_stmt {
|
|
ast.ExprStmt {
|
|
it_expr_type := typeof(it.expr)
|
|
if it_expr_type == 'v.ast.CallExpr' {
|
|
g.writeln('\t // typeof it_expr_type: $it_expr_type')
|
|
last_expr_result_type = g.type_of_call_expr(it.expr)
|
|
} else {
|
|
last_expr_result_type = it_expr_type
|
|
}
|
|
}
|
|
else {
|
|
last_expr_result_type = last_type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
g.writeln('\t// last_type: $last_type')
|
|
g.writeln('\t// last_expr_result_type: $last_expr_result_type')
|
|
return last_type, last_expr_result_type
|
|
}
|
|
|
|
fn (g mut Gen) type_of_call_expr(node ast.Expr) string {
|
|
match node {
|
|
ast.CallExpr {
|
|
return g.typ(it.return_type)
|
|
}
|
|
else {
|
|
return typeof(node)
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
|
|
// `a in [1,2,3]` => `a == 1 || a == 2 || a == 3`
|
|
fn (g mut Gen) in_optimization(left ast.Expr, right ast.ArrayInit) {
|
|
is_str := right.elem_type == table.string_type
|
|
for i, array_expr in right.exprs {
|
|
if is_str {
|
|
g.write('string_eq(')
|
|
}
|
|
g.expr(left)
|
|
if is_str {
|
|
g.write(', ')
|
|
} else {
|
|
g.write(' == ')
|
|
}
|
|
g.expr(array_expr)
|
|
if is_str {
|
|
g.write(')')
|
|
}
|
|
if i < right.exprs.len - 1 {
|
|
g.write(' || ')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn op_to_fn_name(name string) string {
|
|
return match name {
|
|
'+' {
|
|
'_op_plus'
|
|
}
|
|
'-' {
|
|
'_op_minus'
|
|
}
|
|
'*' {
|
|
'_op_mul'
|
|
}
|
|
'/' {
|
|
'_op_div'
|
|
}
|
|
'%' {
|
|
'_op_mod'
|
|
}
|
|
else {
|
|
'bad op $name'
|
|
}
|
|
}
|
|
}
|
|
|
|
fn comp_if_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__'
|
|
}
|
|
'tinyc' {
|
|
return 'tinyc'
|
|
}
|
|
'debug' {
|
|
return '_VDEBUG'
|
|
}
|
|
'linux_or_macos' {
|
|
return ''
|
|
}
|
|
'mingw' {
|
|
return '__MINGW32__'
|
|
}
|
|
'glibc' {
|
|
return '__GLIBC__'
|
|
}
|
|
'prealloc' {
|
|
return 'VPREALLOC'
|
|
}
|
|
'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"')
|
|
}
|
|
}
|
|
// verror('bad os ifdef name "$name"')
|
|
return ''
|
|
}
|
|
|
|
[inline]
|
|
fn c_name(name_ string) string {
|
|
name := name_.replace('.', '__')
|
|
if name in c_reserved {
|
|
return 'v_$name'
|
|
}
|
|
return name
|
|
}
|
|
|
|
fn (g Gen) type_default(typ table.Type) string {
|
|
sym := g.table.get_type_symbol(typ)
|
|
if sym.kind == .array {
|
|
elem_sym := g.table.get_type_symbol(sym.array_info().elem_type)
|
|
elem_type_str := elem_sym.name.replace('.', '__')
|
|
return 'new_array(0, 1, sizeof($elem_type_str))'
|
|
}
|
|
if sym.kind == .map {
|
|
value_sym := g.table.get_type_symbol(sym.map_info().value_type)
|
|
value_type_str := value_sym.name.replace('.', '__')
|
|
return 'new_map(1, sizeof($value_type_str))'
|
|
}
|
|
// Always set pointers to 0
|
|
if table.type_is_ptr(typ) {
|
|
return '0'
|
|
}
|
|
// User struct defined in another module.
|
|
// if typ.contains('__') {
|
|
if sym.kind == .struct_ {
|
|
return '{0}'
|
|
}
|
|
// if typ.ends_with('Fn') { // TODO
|
|
// return '0'
|
|
// }
|
|
// Default values for other types are not needed because of mandatory initialization
|
|
idx := int(typ)
|
|
if idx >= 1 && idx <= 17 {
|
|
return '0'
|
|
}
|
|
/*
|
|
match idx {
|
|
table.bool_type_idx {
|
|
return '0'
|
|
}
|
|
else {}
|
|
}
|
|
*/
|
|
match sym.name {
|
|
'string' {
|
|
return 'tos3("")'
|
|
}
|
|
'rune' {
|
|
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} '}
|
|
}
|
|
*/
|
|
}
|
|
|
|
pub fn (g mut Gen) write_tests_main() {
|
|
g.definitions.writeln('int g_test_oks = 0;')
|
|
g.definitions.writeln('int g_test_fails = 0;')
|
|
$if windows {
|
|
g.writeln('int wmain() {')
|
|
} $else {
|
|
g.writeln('int main() {')
|
|
}
|
|
g.writeln('\t_vinit();')
|
|
g.writeln('')
|
|
all_tfuncs := g.get_all_test_function_names()
|
|
if g.pref.is_stats {
|
|
g.writeln('\tBenchedTests bt = start_testing(${all_tfuncs.len}, tos3("$g.pref.path"));')
|
|
}
|
|
for t in all_tfuncs {
|
|
g.writeln('')
|
|
if g.pref.is_stats {
|
|
g.writeln('\tBenchedTests_testing_step_start(&bt, tos3("$t"));')
|
|
}
|
|
g.writeln('\t${t}();')
|
|
if g.pref.is_stats {
|
|
g.writeln('\tBenchedTests_testing_step_end(&bt);')
|
|
}
|
|
}
|
|
g.writeln('')
|
|
if g.pref.is_stats {
|
|
g.writeln('\tBenchedTests_end_testing(&bt);')
|
|
}
|
|
g.writeln('')
|
|
if g.autofree {
|
|
g.writeln('\t_vcleanup();')
|
|
}
|
|
g.writeln('\treturn g_test_fails > 0;')
|
|
g.writeln('}')
|
|
}
|
|
|
|
fn (g Gen) get_all_test_function_names() []string {
|
|
mut tfuncs := []string
|
|
mut tsuite_begin := ''
|
|
mut tsuite_end := ''
|
|
for _, f in g.table.fns {
|
|
if f.name == 'testsuite_begin' {
|
|
tsuite_begin = f.name
|
|
continue
|
|
}
|
|
if f.name == 'testsuite_end' {
|
|
tsuite_end = f.name
|
|
continue
|
|
}
|
|
if f.name.starts_with('test_') {
|
|
tfuncs << f.name
|
|
continue
|
|
}
|
|
// What follows is for internal module tests
|
|
// (they are part of a V module, NOT in main)
|
|
if f.name.contains('.test_') {
|
|
tfuncs << f.name
|
|
continue
|
|
}
|
|
if f.name.ends_with('.testsuite_begin') {
|
|
tsuite_begin = f.name
|
|
continue
|
|
}
|
|
if f.name.ends_with('.testsuite_end') {
|
|
tsuite_end = f.name
|
|
continue
|
|
}
|
|
}
|
|
mut all_tfuncs := []string
|
|
if tsuite_begin.len > 0 {
|
|
all_tfuncs << tsuite_begin
|
|
}
|
|
all_tfuncs << tfuncs
|
|
if tsuite_end.len > 0 {
|
|
all_tfuncs << tsuite_end
|
|
}
|
|
mut all_tfuncs_c := []string
|
|
for f in all_tfuncs {
|
|
all_tfuncs_c << f.replace('.', '__')
|
|
}
|
|
return all_tfuncs_c
|
|
}
|
|
|
|
fn (g Gen) is_importing_os() bool {
|
|
return 'os' in g.table.imports
|
|
}
|
|
|
|
fn (g mut Gen) comp_if(it ast.CompIf) {
|
|
ifdef := comp_if_to_ifdef(it.val)
|
|
if it.is_not {
|
|
g.writeln('\n#ifndef ' + ifdef)
|
|
g.writeln('// #if not $it.val')
|
|
} else {
|
|
g.writeln('\n#ifdef ' + ifdef)
|
|
g.writeln('// #if $it.val')
|
|
}
|
|
// NOTE: g.defer_ifdef is needed for defers called witin an ifdef
|
|
// in v1 this code would be completely excluded
|
|
g.defer_ifdef = if it.is_not {
|
|
'#ifndef ' + ifdef
|
|
} else {
|
|
'#ifdef ' + ifdef
|
|
}
|
|
// println('comp if stmts $g.file.path:$it.pos.line_nr')
|
|
g.stmts(it.stmts)
|
|
g.defer_ifdef = ''
|
|
if it.has_else {
|
|
g.writeln('#else')
|
|
g.defer_ifdef = if it.is_not {
|
|
'#ifdef ' + ifdef
|
|
} else {
|
|
'#ifndef ' + ifdef
|
|
}
|
|
g.stmts(it.else_stmts)
|
|
g.defer_ifdef = ''
|
|
}
|
|
g.writeln('#endif')
|
|
}
|
|
|
|
fn (g mut Gen) go_stmt(node ast.GoStmt) {
|
|
tmp := g.new_tmp_var()
|
|
// x := node.call_expr as ast.CallEpxr // TODO
|
|
match node.call_expr {
|
|
ast.CallExpr {
|
|
mut name := it.name.replace('.', '__')
|
|
if it.is_method {
|
|
receiver_sym := g.table.get_type_symbol(it.receiver_type)
|
|
name = receiver_sym.name + '_' + name
|
|
}
|
|
g.writeln('// go')
|
|
wrapper_struct_name := 'thread_arg_' + name
|
|
wrapper_fn_name := name + '_thread_wrapper'
|
|
arg_tmp_var := 'arg_' + tmp
|
|
g.writeln('$wrapper_struct_name *$arg_tmp_var = malloc(sizeof(thread_arg_$name));')
|
|
if it.is_method {
|
|
g.write('${arg_tmp_var}->arg0 = ')
|
|
g.expr(it.left)
|
|
g.writeln(';')
|
|
}
|
|
for i, arg in it.args {
|
|
g.write('${arg_tmp_var}->arg${i+1} = ')
|
|
g.expr(arg.expr)
|
|
g.writeln(';')
|
|
}
|
|
if g.pref.os == .windows {
|
|
g.writeln('CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0,0);')
|
|
} else {
|
|
g.writeln('pthread_t thread_$tmp;')
|
|
g.writeln('pthread_create(&thread_$tmp, NULL, (void*)$wrapper_fn_name, $arg_tmp_var);')
|
|
}
|
|
g.writeln('// endgo\n')
|
|
// Register the wrapper type and function
|
|
if name in g.threaded_fns {
|
|
return
|
|
}
|
|
g.definitions.writeln('\ntypedef struct $wrapper_struct_name {')
|
|
if it.is_method {
|
|
styp := g.typ(it.receiver_type)
|
|
g.definitions.writeln('\t$styp arg0;')
|
|
}
|
|
for i, arg in it.args {
|
|
styp := g.typ(arg.typ)
|
|
g.definitions.writeln('\t$styp arg${i+1};')
|
|
}
|
|
g.definitions.writeln('} $wrapper_struct_name;')
|
|
g.definitions.writeln('void* ${wrapper_fn_name}($wrapper_struct_name *arg);')
|
|
g.gowrappers.writeln('void* ${wrapper_fn_name}($wrapper_struct_name *arg) {')
|
|
g.gowrappers.write('\t${name}(')
|
|
if it.is_method {
|
|
g.gowrappers.write('arg->arg0')
|
|
if it.args.len > 0 {
|
|
g.gowrappers.write(', ')
|
|
}
|
|
}
|
|
for i in 0 .. it.args.len {
|
|
g.gowrappers.write('arg->arg${i+1}')
|
|
if i < it.args.len - 1 {
|
|
g.gowrappers.write(', ')
|
|
}
|
|
}
|
|
g.gowrappers.writeln(');')
|
|
g.gowrappers.writeln('\treturn 0;')
|
|
g.gowrappers.writeln('}')
|
|
g.threaded_fns << name
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
|
|
// already generated styp, reuse it
|
|
fn (g mut Gen) gen_str_for_type(sym table.TypeSymbol, styp string) {
|
|
if sym.has_method('str') || styp in g.str_types {
|
|
return
|
|
}
|
|
g.str_types << styp
|
|
match sym.info {
|
|
table.Struct {
|
|
g.gen_str_for_struct(it, styp)
|
|
}
|
|
table.Enum {
|
|
g.gen_str_for_enum(it, styp)
|
|
}
|
|
else {
|
|
println('cannot generate str() for $sym.name')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (g mut Gen) gen_str_for_enum(info table.Enum, styp string) {
|
|
s := styp.replace('.', '__')
|
|
g.definitions.write('string ${s}_str($styp it) {\n\tswitch(it) {\n')
|
|
for i, val in info.vals {
|
|
g.definitions.write('\t\tcase ${s}_$val: return tos3("$val");\n')
|
|
}
|
|
g.definitions.write('\t\tdefault: return tos3("unknown enum value"); } }\n')
|
|
}
|
|
|
|
fn (g mut Gen) gen_str_for_struct(info table.Struct, styp string) {
|
|
// TODO: short it if possible
|
|
// generates all definitions of substructs
|
|
for i, field in info.fields {
|
|
sym := g.table.get_type_symbol(field.typ)
|
|
if sym.kind == .struct_ {
|
|
field_styp := g.typ(field.typ)
|
|
g.gen_str_for_type(sym, field_styp)
|
|
}
|
|
}
|
|
s := styp.replace('.', '__')
|
|
g.definitions.write('string ${s}_str($styp it, int indent_count) {\n')
|
|
// generate ident / indent length = 4 spaces
|
|
g.definitions.write('\tstring indents = tos3("");\n\tfor (int i = 0; i < indent_count; i++) { indents = string_add(indents, tos3(" ")); }\n')
|
|
g.definitions.write('\treturn _STR("$styp {\\n')
|
|
for field in info.fields {
|
|
fmt := g.type_to_fmt(field.typ)
|
|
g.definitions.write('%.*s ' + '$field.name: $fmt\\n')
|
|
}
|
|
g.definitions.write('%.*s}"')
|
|
if info.fields.len > 0 {
|
|
g.definitions.write(', ')
|
|
for i, field in info.fields {
|
|
sym := g.table.get_type_symbol(field.typ)
|
|
if sym.kind == .struct_ {
|
|
field_styp := g.typ(field.typ)
|
|
g.definitions.write('indents.len, indents.str, ${field_styp}_str(it.$field.name, indent_count + 1).len, ${field_styp}_str(it.$field.name, indent_count + 1).str')
|
|
} else {
|
|
g.definitions.write('indents.len, indents.str, it.$field.name')
|
|
if field.typ == table.string_type {
|
|
g.definitions.write('.len, it.${field.name}.str')
|
|
} else if field.typ == table.bool_type {
|
|
g.definitions.write(' ? 4 : 5, it.${field.name} ? "true" : "false"')
|
|
}
|
|
if i < info.fields.len - 1 {
|
|
g.definitions.write(', ')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
g.definitions.writeln(', indents.len, indents.str);\n}')
|
|
}
|
|
|
|
fn (g Gen) type_to_fmt(typ table.Type) string {
|
|
sym := g.table.get_type_symbol(typ)
|
|
if sym.kind == .struct_ {
|
|
return '%.*s'
|
|
} else if typ == table.string_type {
|
|
return "\'%.*s\'"
|
|
} else if typ == table.bool_type {
|
|
return '%.*s'
|
|
} else if typ in [table.f32_type, table.f64_type] {
|
|
return '%g' // g removes trailing zeros unlike %f
|
|
}
|
|
return '%d'
|
|
}
|