v/compiler/table.v

988 lines
22 KiB
V

// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import math
import strings
struct Table {
mut:
typesmap map[string]Type
consts []Var
fns map[string]Fn
generic_fns []GenTable //map[string]GenTable // generic_fns['listen_and_serve'] == ['Blog', 'Forum']
obf_ids map[string]int // obf_ids['myfunction'] == 23
modules []string // List of all modules registered by the application
imports []string // List of all imports
file_imports []FileImportTable // List of imports for file
cflags []CFlag // ['-framework Cocoa', '-lglfw3']
fn_cnt int //atomic
obfuscate bool
}
struct GenTable {
fn_name string
mut:
types []string
}
// Holds import information scoped to the parsed file
struct FileImportTable {
mut:
module_name string
file_path string
imports map[string]string
}
enum AccessMod {
private // private immutable
private_mut // private mutable
public // public immutable (readonly)
public_mut // public, but mutable only in this module
public_mut_mut // public and mutable both inside and outside (not recommended to use, that's why it's so verbose)
}
enum TypeCategory {
builtin
struct_
func // 2
interface_
enum_
union_ // 5
c_struct
c_typedef
array
}
struct Var {
mut:
typ string
name string
is_arg bool
is_const bool
args []Var // function args
attr string // [json] etc
is_mut bool
is_alloc bool
is_returned bool
ptr bool
ref bool
parent_fn string // Variables can only be defined in functions
mod string // module where this var is stored
line_nr int
access_mod AccessMod
is_global bool // __global (translated from C only)
is_used bool
is_changed bool
scope_level int
is_c bool // todo remove once `typ` is `Type`, not string
}
struct Type {
mut:
mod string
name string
cat TypeCategory
fields []Var
methods []Fn
parent string
func Fn // For cat == FN (type myfn fn())
is_c bool // `C.FILE`
enum_vals []string
gen_types []string
// This field is used for types that are not defined yet but are known to exist.
// It allows having things like `fn (f Foo) bar()` before `Foo` is defined.
// This information is needed in the first pass.
is_placeholder bool
gen_str bool // needs `.str()` method generation
}
struct TypeNode {
mut:
next &TypeNode
typ Type
}
// For debugging types
fn (t Type) str() string {
mut s := 'type "$t.name" {'
if t.fields.len > 0 {
// s += '\n $t.fields.len fields:\n'
for field in t.fields {
s += '\n $field.name $field.typ'
}
s += '\n'
}
if t.methods.len > 0 {
// s += '\n $t.methods.len methods:\n'
for method in t.methods {
s += '\n ${method.str()}'
}
s += '\n'
}
s += '}\n'
return s
}
const (
CReserved = [
'exit',
'unix',
//'print',
// 'ok',
'error',
'malloc',
'calloc',
'free',
'panic',
// Full list of C reserved words, from: https://en.cppreference.com/w/c/keyword
'auto',
'break',
'case',
'char',
'const',
'continue',
'default',
'do',
'double',
'else',
'enum',
'extern',
'float',
'for',
'goto',
'if',
'inline',
'int',
'long',
'register',
'restrict',
'return',
'short',
'signed',
'sizeof',
'static',
'struct',
'switch',
'typedef',
'union',
'unsigned',
'void',
'volatile',
'while',
]
)
// This is used in generated C code
fn (f Fn) str() string {
t := Table{}
str_args := f.str_args(t)
return '$f.name($str_args) $f.typ'
}
fn (t &Table) debug_fns() string {
mut s := strings.new_builder(1000)
for _, f in t.fns {
s.writeln(f.name)
}
return s.str()
}
// fn (types array_Type) print_to_file(f string) {
// }
const (
number_types = ['number', 'int', 'i8', 'i16', 'u16', 'u32', 'byte', 'i64', 'u64', 'f32', 'f64']
float_types = ['f32', 'f64']
)
fn is_number_type(typ string) bool {
return typ in number_types
}
fn is_float_type(typ string) bool {
return typ in float_types
}
fn is_primitive_type(typ string) bool {
return is_number_type(typ) || typ == 'string'
}
fn new_table(obfuscate bool) &Table {
mut t := &Table {
obfuscate: obfuscate
}
t.register_type('int')
t.register_type('size_t')
t.register_type_with_parent('i8', 'int')
t.register_type_with_parent('byte', 'int')
t.register_type_with_parent('i16', 'int')
t.register_type_with_parent('u16', 'u32')
t.register_type_with_parent('u32', 'int')
t.register_type_with_parent('i64', 'int')
t.register_type_with_parent('u64', 'u32')
t.register_type('byteptr')
t.register_type('intptr')
t.register_type('f32')
t.register_type('f64')
t.register_type('rune')
t.register_type('bool')
t.register_type('void')
t.register_type('voidptr')
t.register_type('T')
t.register_type('va_list')
t.register_const('stdin', 'int', 'main')
t.register_const('stdout', 'int', 'main')
t.register_const('stderr', 'int', 'main')
t.register_const('errno', 'int', 'main')
t.register_type_with_parent('map_string', 'map')
t.register_type_with_parent('map_int', 'map')
return t
}
// If `name` is a reserved C keyword, returns `v_name` instead.
fn (t mut Table) var_cgen_name(name string) string {
if name in CReserved {
return 'v_$name'
}
else {
return name
}
}
fn (t mut Table) register_module(mod string) {
if mod in t.modules {
return
}
t.modules << mod
}
fn (p mut Parser) register_array(typ string) {
if typ.contains('*') {
println('bad arr $typ')
return
}
if !p.table.known_type(typ) {
p.register_type_with_parent(typ, 'array')
p.cgen.typedefs << 'typedef array $typ;'
}
}
fn (p mut Parser) register_map(typ string) {
if typ.contains('*') {
println('bad map $typ')
return
}
if !p.table.known_type(typ) {
p.register_type_with_parent(typ, 'map')
p.cgen.typedefs << 'typedef map $typ;'
}
}
fn (table &Table) known_mod(mod string) bool {
return mod in table.modules
}
fn (t mut Table) register_const(name, typ, mod string) {
t.consts << Var {
name: name
typ: typ
is_const: true
mod: mod
}
}
// Only for translated code
fn (p mut Parser) register_global(name, typ string) {
p.table.consts << Var {
name: name
typ: typ
is_const: true
is_global: true
mod: p.mod
is_mut: true
}
}
fn (t mut Table) register_fn(new_fn Fn) {
t.fns[new_fn.name] = new_fn
}
fn (table &Table) known_type(typ_ string) bool {
mut typ := typ_
// 'byte*' => look up 'byte', but don't mess up fns
if typ.ends_with('*') && !typ.contains(' ') {
typ = typ.left(typ.len - 1)
}
t := table.typesmap[typ]
return t.name.len > 0 && !t.is_placeholder
}
fn (table &Table) known_type_fast(t &Type) bool {
return t.name.len > 0 && !t.is_placeholder
}
fn (t &Table) find_fn(name string) Fn {
f := t.fns[name]
if !isnil(f.name.str) {
return f
}
return Fn{}
}
fn (t &Table) known_fn(name string) bool {
f := t.find_fn(name)
return f.name != ''
}
fn (t &Table) known_const(name string) bool {
v := t.find_const(name)
// TODO use optional
return v.name.len > 0
}
fn (t mut Table) register_type(typ string) {
if typ.len == 0 {
return
}
if typ in t.typesmap {
return
}
t.typesmap[typ] = Type{name:typ}
}
fn (p mut Parser) register_type_with_parent(strtyp, parent string) {
typ := Type {
name: strtyp
parent: parent
mod: p.mod
}
p.table.register_type2(typ)
}
fn (t mut Table) register_type_with_parent(typ, parent string) {
if typ.len == 0 {
return
}
t.typesmap[typ] = Type {
name: typ
parent: parent
//mod: mod
}
}
fn (t mut Table) register_type2(typ Type) {
if typ.name.len == 0 {
return
}
t.typesmap[typ.name] = typ
}
fn (t mut Table) rewrite_type(typ Type) {
if typ.name.len == 0 {
return
}
t.typesmap[typ.name] = typ
}
fn (table mut Table) add_field(type_name, field_name, field_type string, is_mut bool, attr string, access_mod AccessMod) {
if type_name == '' {
print_backtrace()
cerror('add_field: empty type')
}
mut t := table.typesmap[type_name]
t.fields << Var {
name: field_name
typ: field_type
is_mut: is_mut
attr: attr
parent_fn: type_name // Name of the parent type
access_mod: access_mod
}
table.typesmap[type_name] = t
}
fn (t &Type) has_field(name string) bool {
field := t.find_field(name)
return (field.name != '')
}
fn (t &Type) has_enum_val(name string) bool {
return name in t.enum_vals
}
fn (t &Type) find_field(name string) Var {
for field in t.fields {
if field.name == name {
return field
}
}
return Var{}
}
fn (table &Table) type_has_field(typ &Type, name string) bool {
field := table.find_field(typ, name)
return (field.name != '')
}
fn (table &Table) find_field(typ &Type, name string) Var {
field := typ.find_field(name)
if field.name.len == 0 && typ.parent.len > 0 {
parent := table.find_type(typ.parent)
return parent.find_field(name)
}
return field
}
fn (table mut Table) add_method(type_name string, f Fn) {
if type_name == '' {
print_backtrace()
cerror('add_method: empty type')
}
// TODO table.typesmap[type_name].methods << f
mut t := table.typesmap[type_name]
t.methods << f
table.typesmap[type_name] = t
}
fn (t &Type) has_method(name string) bool {
method := t.find_method(name)
return (method.name != '')
}
fn (table &Table) type_has_method(typ &Type, name string) bool {
method := table.find_method(typ, name)
return (method.name != '')
}
// TODO use `?Fn`
fn (table &Table) find_method(typ &Type, name string) Fn {
// println('TYPE HAS METHOD $name')
// method := typ.find_method(name)
t := table.typesmap[typ.name]
method := t.find_method(name)
if method.name.len == 0 && typ.parent.len > 0 {
parent := table.find_type(typ.parent)
return parent.find_method(name)
// println('parent = $parent.name $res')
// return res
}
return method
}
fn (t &Type) find_method(name string) Fn {
// println('$t.name find_method($name) methods.len=$t.methods.len')
for method in t.methods {
// println('method=$method.name')
if method.name == name {
return method
}
}
return Fn{}
}
/*
// TODO
fn (t mutt Type) add_gen_type(type_name string) {
// println('add_gen_type($s)')
if t.gen_types.contains(type_name) {
return
}
t.gen_types << type_name
}
*/
fn (p &Parser) find_type(name string) Type {
typ := p.table.find_type(name)
if typ.name == '' {
return p.table.find_type(p.prepend_mod(name))
}
return typ
}
fn (t &Table) find_type(name_ string) Type {
mut name := name_
if name.ends_with('*') && !name.contains(' ') {
name = name.left(name.len - 1)
}
if !(name in t.typesmap) {
return Type{}
}
return t.typesmap[name]
}
fn (p mut Parser) _check_types(got_, expected_ string, throw bool) bool {
mut got := got_
mut expected := expected_
p.log('check types got="$got" exp="$expected" ')
if p.pref.translated {
return true
}
// Allow ints to be used as floats
if got == 'int' && expected == 'f32' {
return true
}
if got == 'int' && expected == 'f64' {
return true
}
if got == 'f64' && expected == 'f32' {
return true
}
if got == 'f32' && expected == 'f64' {
return true
}
// Allow ints to be used as longs
if got=='int' && expected=='i64' {
return true
}
if got == 'void*' && expected.starts_with('fn ') {
return true
}
if got.starts_with('[') && expected == 'byte*' {
return true
}
// Todo void* allows everything right now
if got=='void*' || expected=='void*' {// || got == 'cvoid' || expected == 'cvoid' {
return true
}
// TODO only allow numeric consts to be assigned to bytes, and
// throw an error if they are bigger than 255
if got=='int' && expected=='byte' {
return true
}
if got=='byteptr' && expected=='byte*' {
return true
}
if got=='byte*' && expected=='byteptr' {
return true
}
if got=='int' && expected=='byte*' {
return true
}
//if got=='int' && expected=='voidptr*' {
//return true
//}
// byteptr += int
if got=='int' && expected=='byteptr' {
return true
}
if got == 'Option' && expected.starts_with('Option_') {
return true
}
// lines := new_array
if got == 'array' && expected.starts_with('array_') {
return true
}
// Expected type "Option_os__File", got "os__File"
if expected.starts_with('Option_') && expected.ends_with(got) {
return true
}
// NsColor* return 0
if expected.ends_with('*') && got == 'int' {
return true
}
// if got == 'T' || got.contains('<T>') {
// return true
// }
// if expected == 'T' || expected.contains('<T>') {
// return true
// }
// Allow pointer arithmetic
if expected=='void*' && got=='int' {
return true
}
expected = expected.replace('*', '')
got = got.replace('*', '')
if got != expected {
// Interface check
if expected.ends_with('er') {
if p.satisfies_interface(expected, got, throw) {
return true
}
}
if !throw {
return false
}
else {
p.error('expected type `$expected`, but got `$got`')
}
}
return true
}
// throw by default
fn (p mut Parser) check_types(got, expected string) bool {
if p.first_pass() { return true }
return p._check_types(got, expected, true)
}
fn (p mut Parser) check_types_no_throw(got, expected string) bool {
return p._check_types(got, expected, false)
}
fn (p mut Parser) satisfies_interface(interface_name, _typ string, throw bool) bool {
int_typ := p.table.find_type(interface_name)
typ := p.table.find_type(_typ)
for method in int_typ.methods {
if !typ.has_method(method.name) {
// if throw {
p.error('Type "$_typ" doesn\'t satisfy interface "$interface_name" (method "$method.name" is not implemented)')
// }
return false
}
}
return true
}
fn type_default(typ string) string {
if typ.starts_with('array_') {
return 'new_array(0, 1, sizeof( ${typ.right(6)} ))'
}
// Always set pointers to 0
if typ.ends_with('*') {
return '0'
}
// User struct defined in another module.
if typ.contains('__') {
return '{0}'
}
// Default values for other types are not needed because of mandatory initialization
switch typ {
case 'bool': return '0'
case 'string': return 'tos((byte *)"", 0)'
case 'i8': return '0'
case 'i16': return '0'
case 'i64': return '0'
case 'u16': return '0'
case 'u32': return '0'
case 'u64': return '0'
case 'byte': return '0'
case 'int': return '0'
case 'rune': return '0'
case 'f32': return '0.0'
case 'f64': return '0.0'
case 'byteptr': return '0'
case 'voidptr': return '0'
}
return '{0}'
}
fn (table &Table) is_interface(name string) bool {
if !(name in table.typesmap) {
return false
}
t := table.typesmap[name]
return t.cat == .interface_
}
// Do we have fn main()?
fn (t &Table) main_exists() bool {
for _, f in t.fns {
if f.name == 'main' {
return true
}
}
return false
}
// TODO use `?Var`
fn (t &Table) find_const(name string) Var {
//println('find const l=$t.consts.len')
for c in t.consts {
if c.name == name {
return c
}
}
return Var{}
}
fn (table mut Table) cgen_name(f &Fn) string {
mut name := f.name
if f.is_method {
name = '${f.receiver_typ}_$f.name'
name = name.replace(' ', '')
name = name.replace('*', '')
name = name.replace('+', 'plus')
name = name.replace('-', 'minus')
}
// Avoid name conflicts (with things like abs(), print() etc).
// Generate b_abs(), b_print()
// TODO duplicate functionality
if f.mod == 'builtin' && f.name in CReserved {
return 'v_$name'
}
// Obfuscate but skip certain names
// TODO ugly, fix
if table.obfuscate && f.name != 'main' && f.name != 'WinMain' && f.mod != 'builtin' && !f.is_c &&
f.mod != 'darwin' && f.mod != 'os' && !f.name.contains('window_proc') && f.name != 'gg__vec2' &&
f.name != 'build_token_str' && f.name != 'build_keys' && f.mod != 'json' &&
!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
}
// ('s', 'string') => 'string s'
// ('nums', '[20]byte') => 'byte nums[20]'
// ('myfn', 'fn(int) string') => 'string (*myfn)(int)'
fn (table &Table) cgen_name_type_pair(name, typ string) string {
// Special case for [10]int
if typ.len > 0 && typ[0] == `[` {
tmp := typ.all_after(']')
size := typ.all_before(']')
return '$tmp $name $size ]'
}
// fn()
else if typ.starts_with('fn (') {
T := table.find_type(typ)
if T.name == '' {
println('this should never happen')
exit(1)
}
str_args := T.func.str_args(table)
return '$T.func.typ (*$name)( $str_args /*FFF*/ )'
}
// TODO tm hack, do this for all C struct args
else if typ == 'tm' {
return 'struct /*TM*/ tm $name'
}
return '$typ $name'
}
fn is_valid_int_const(val, typ string) bool {
x := val.int()
switch typ {
case 'byte': return 0 <= x && x <= math.MaxU8
case 'u16': return 0 <= x && x <= math.MaxU16
//case 'u32': return 0 <= x && x <= math.MaxU32
//case 'u64': return 0 <= x && x <= math.MaxU64
//////////////
case 'i8': return math.MinI8 <= x && x <= math.MaxI8
case 'i16': return math.MinI16 <= x && x <= math.MaxI16
case 'int': return math.MinI32 <= x && x <= math.MaxI32
//case 'i64':
//x64 := val.i64()
//return i64(-(1<<63)) <= x64 && x64 <= i64((1<<63)-1)
}
return true
}
fn (t mut Table) register_generic_fn(fn_name string) {
t.generic_fns << GenTable{fn_name, []string}
}
fn (t mut Table) fn_gen_types(fn_name string) []string {
for _, f in t.generic_fns {
if f.fn_name == fn_name {
return f.types
}
}
cerror('function $fn_name not found')
return []string
}
// `foo<Bar>()`
// fn_name == 'foo'
// typ == 'Bar'
fn (t mut Table) register_generic_fn_type(fn_name, typ string) {
for i, f in t.generic_fns {
if f.fn_name == fn_name {
t.generic_fns[i].types << typ
return
}
}
}
fn (p mut Parser) typ_to_fmt(typ string, level int) string {
t := p.table.find_type(typ)
if t.cat == .enum_ {
return '%d'
}
switch typ {
case 'string': return '%.*s'
//case 'bool': return '%.*s'
case 'ustring': return '%.*s'
case 'byte', 'bool', 'int', 'char', 'byte', 'i16', 'i8': return '%d'
case 'u16', 'u32': return '%u'
case 'f64', 'f32': return '%f'
case 'i64': return '%lld'
case 'u64': return '%llu'
case 'byte*', 'byteptr': return '%s'
// case 'array_string': return '%s'
// case 'array_int': return '%s'
case 'void': p.error('cannot interpolate this value')
default:
if typ.ends_with('*') {
return '%p'
}
}
if t.parent != '' && level == 0 {
return p.typ_to_fmt(t.parent, level+1)
}
return ''
}
fn is_compile_time_const(s_ string) bool {
s := s_.trim_space()
if s == '' {
return false
}
if s.contains('\'') {
return true
}
for c in s {
if ! ((c >= `0` && c <= `9`) || c == `.`) {
return false
}
}
return true
}
// 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('/')
if mod == m_parts[m_parts.len-1] && file_path.contains(m_path) {
return m
}
}
}
return mod
}
fn new_file_import_table(file_path string) &FileImportTable {
return &FileImportTable{
file_path: file_path
imports: map[string]string
}
}
fn (fit &FileImportTable) known_import(mod string) bool {
return mod in fit.imports || fit.is_aliased(mod)
}
fn (fit mut FileImportTable) register_import(mod string) {
fit.register_alias(mod, mod)
}
fn (fit mut FileImportTable) register_alias(alias string, mod string) {
if alias in fit.imports {
cerror('cannot import $mod as $alias: import name $alias already in use in "${fit.file_path}".')
}
if mod.contains('.internal.') {
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 !fit.module_name.starts_with(internal_parent) {
cerror('module $mod can only be imported internally by libs.')
}
}
fit.imports[alias] = mod
}
fn (fit &FileImportTable) known_alias(alias string) bool {
return alias in fit.imports
}
fn (fit &FileImportTable) is_aliased(mod string) bool {
for _, val in fit.imports {
if val == mod {
return true
}
}
return false
}
fn (fit &FileImportTable) resolve_alias(alias string) string {
return fit.imports[alias]
}
fn (t &Type) contains_field_type(typ string) bool {
if !t.name[0].is_capital() {
return false
}
for field in t.fields {
if field.typ == typ {
return true
}
}
return false
}
// check for a function / variable / module typo in `name`
fn (table &Table) identify_typo(name string, current_fn &Fn, fit &FileImportTable) string {
// dont check if so short
if name.len < 2 { return '' }
min_match := 0.8 // for dice coefficient between 0.0 - 1.0
name_orig := name.replace('__', '.').replace('_dot_', '.')
mut output := ''
// check functions
mut n := table.find_misspelled_fn(name_orig, min_match)
if n != '' {
output += '\n * function: `$n`'
}
// check function local variables
n = current_fn.find_misspelled_local_var(name_orig, min_match)
if n != '' {
output += '\n * variable: `$n`'
}
// check imported modules
n = table.find_misspelled_imported_mod(name_orig, fit, min_match)
if n != '' {
output += '\n * module: `$n`'
}
return output
}
// find function with closest name to `name`
fn (table &Table) find_misspelled_fn(name string, min_match f32) string {
mut closest := f32(0)
mut closest_fn := ''
for _, f in table.fns {
n := '${f.mod}.$f.name'
if !name.starts_with(f.mod) || (n.len - name.len > 3 || name.len - n.len > 3) { continue }
p := strings.dice_coefficient(name, n)
if p > closest {
closest = p
closest_fn = n
}
}
return if closest >= min_match { closest_fn } else { '' }
}
// find imported module with closest name to `name`
fn (table &Table) find_misspelled_imported_mod(name string, fit &FileImportTable, min_match f32) string {
mut closest := f32(0)
mut closest_mod := ''
for alias, mod in fit.imports {
n := '${fit.module_name}.$alias'
if !name.starts_with(fit.module_name) || (n.len - name.len > 3 || name.len - n.len > 3) { continue }
p := strings.dice_coefficient(name, n)
if p > closest {
closest = p
closest_mod = '$alias ($mod)'
}
}
return if closest >= min_match { closest_mod } else { '' }
}