1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

vlib: add a v.reflection module for reflection done at runtime (#17072)

This commit is contained in:
Felipe Pena 2023-01-25 17:01:22 -03:00 committed by GitHub
parent e32ed368ca
commit 84b99ceeb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 476 additions and 1 deletions

View File

@ -236,6 +236,12 @@ mut:
// out_idx int
out_fn_start_pos []int // for generating multiple .c files, stores locations of all fn positions in `out` string builder
static_modifier string // for parallel_cc
has_reflection bool
// reflection metadata initialization
reflection_funcs strings.Builder
reflection_others strings.Builder
reflection_strings &map[string]int
}
// global or const variable definition string
@ -264,6 +270,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string,
$if time_cgening ? {
timers_should_print = true
}
mut reflection_strings := map[string]int{}
mut global_g := Gen{
file: 0
out: strings.new_builder(512000)
@ -306,7 +313,12 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string,
use_segfault_handler: !('no_segfault_handler' in pref.compile_defines
|| pref.os in [.wasm32, .wasm32_emscripten])
static_modifier: if pref.parallel_cc { 'static' } else { '' }
has_reflection: 'v.reflection' in table.modules
reflection_funcs: strings.new_builder(100)
reflection_others: strings.new_builder(100)
reflection_strings: &reflection_strings
}
/*
global_g.out_parallel = []strings.Builder{len: nr_cpus}
for i in 0 .. nr_cpus {
@ -358,6 +370,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string,
global_g.embedded_data.write(g.embedded_data) or { panic(err) }
global_g.shared_types.write(g.shared_types) or { panic(err) }
global_g.shared_functions.write(g.channel_definitions) or { panic(err) }
global_g.reflection_funcs.write(g.reflection_funcs) or { panic(err) }
global_g.force_main_console = global_g.force_main_console || g.force_main_console
@ -455,6 +468,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string,
mut g := global_g
util.timing_start('cgen common')
// to make sure type idx's are the same in cached mods
if g.pref.build_mode == .build_module {
for idx, sym in g.table.type_symbols {
@ -664,6 +678,10 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen {
referenced_fns: global_g.referenced_fns
is_cc_msvc: global_g.is_cc_msvc
use_segfault_handler: global_g.use_segfault_handler
has_reflection: 'v.reflection' in global_g.table.modules
reflection_funcs: strings.new_builder(100)
reflection_others: strings.new_builder(100)
reflection_strings: global_g.reflection_strings
}
g.gen_file()
return g
@ -715,7 +733,6 @@ pub fn (mut g Gen) gen_file() {
g.is_vlines_enabled = true
g.inside_ternary = 0
}
g.stmts(g.file.stmts)
// Transfer embedded files
for path in g.file.embedded_files {
@ -5277,6 +5294,7 @@ fn (mut g Gen) write_init_function() {
// ___argv is declared as voidptr here, because that unifies the windows/unix logic
g.writeln('void _vinit(int ___argc, voidptr ___argv) {')
if g.pref.trace_calls {
g.writeln('\tv__trace_calls__on_call(_SLIT("_vinit"));')
}
@ -5302,7 +5320,20 @@ fn (mut g Gen) write_init_function() {
if g.nr_closures > 0 {
g.writeln('\t_closure_mtx_init();')
}
// reflection bootstraping
if g.has_reflection {
if var := g.global_const_defs['g_reflection'] {
g.writeln(var.init)
g.gen_reflection_data()
}
}
for mod_name in g.table.modules {
if g.has_reflection && mod_name == 'v.reflection' {
// ignore v.reflection already initialized above
continue
}
mut is_empty := true
// write globals and consts init later
for var_name in g.sorted_global_const_names {
@ -5328,6 +5359,7 @@ fn (mut g Gen) write_init_function() {
}
}
}
g.writeln('}')
if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list {
println(g.out.after(fn_vinit_start_pos))

View File

@ -288,6 +288,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
}
fn_header := '${visibility_kw}${type_name} ${fn_attrs}${name}('
g.definitions.write_string(fn_header)
g.gen_reflection_function(node)
g.write(fn_header)
}
arg_start_pos := g.out.len

160
vlib/v/gen/c/reflection.v Normal file
View File

@ -0,0 +1,160 @@
module c
import v.ast
import v.util
// reflection_string maps string to its idx
fn (mut g Gen) reflection_string(str string) int {
return unsafe {
g.reflection_strings[str] or {
g.reflection_strings[str] = g.reflection_strings.len
g.reflection_strings.len - 1
}
}
}
// gen_reflection_strings generates the reflectino string registration
[inline]
fn (mut g Gen) gen_reflection_strings() {
for str, idx in g.reflection_strings {
g.reflection_others.write_string('\tv__reflection__add_string(_SLIT("${str}"), ${idx});\n')
}
}
// gen_empty_array generates code for empty array
[inline]
fn (g Gen) gen_empty_array(type_name string) string {
return '__new_array_with_default(0, 0, sizeof(${type_name}), 0)'
}
// gen_functionarg_array generates the code for functionarg argument
[inline]
fn (g Gen) gen_functionarg_array(type_name string, node ast.FnDecl) string {
if node.params.len == 0 {
return g.gen_empty_array(type_name)
}
mut out := 'new_array_from_c_array(${node.params.len},${node.params.len},sizeof(${type_name}),'
out += '_MOV((${type_name}[${node.params.len}]){'
for param in node.params {
out += '((${type_name}){.name=_SLIT("${param.name}"),.typ=${param.typ.idx()},}),'
}
out += '}))'
return out
}
// gen_functionarg_array generates the code for functionarg argument
[inline]
fn (mut g Gen) gen_function_array(nodes []ast.FnDecl) string {
type_name := 'v__reflection__Function'
if nodes.len == 0 {
return g.gen_empty_array(type_name)
}
mut out := 'new_array_from_c_array(${nodes.len},${nodes.len},sizeof(${type_name}),'
out += '_MOV((${type_name}[${nodes.len}]){'
for method in nodes {
out += g.gen_reflection_fndecl(method)
out += ','
}
out += '}))'
return out
}
// gen_reflection_enum_fields generates C code for enum fields
[inline]
fn (g Gen) gen_reflection_enum_fields(fields []ast.EnumField) string {
if fields.len == 0 {
return g.gen_empty_array('v__reflection__EnumField')
}
mut out := 'new_array_from_c_array(${fields.len},${fields.len},sizeof(v__reflection__EnumField),'
out += '_MOV((v__reflection__EnumField[${fields.len}]){'
for field in fields {
out += '((v__reflection__EnumField){.name=_SLIT("${field.name}")}),'
}
out += '}))'
return out
}
// gen_reflection_fndecl generates C code for function declaration
[inline]
fn (mut g Gen) gen_reflection_fndecl(node ast.FnDecl) string {
mut arg_str := '((v__reflection__Function){'
v_name := node.name.all_after_last('.')
arg_str += '.mod_name=_SLIT("${node.mod}"),'
arg_str += '.name=_SLIT("${v_name}"),'
arg_str += '.args=${g.gen_functionarg_array('v__reflection__FunctionArg', node)},'
arg_str += '.file_idx=${g.reflection_string(util.cescaped_path(node.file))},'
arg_str += '.line_start=${node.pos.line_nr},'
arg_str += '.line_end=${node.pos.last_line},'
arg_str += '.is_variadic=${node.is_variadic},'
arg_str += '.return_typ=${node.return_type.idx()},'
arg_str += '.receiver_typ=${node.receiver.typ.idx()}'
arg_str += '})'
return arg_str
}
// gen_reflection_sym generates C code for TypeSymbol struct
[inline]
fn (g Gen) gen_reflection_sym(tsym ast.TypeSymbol) string {
kind_name := if tsym.kind in [.none_, .struct_, .enum_, .interface_] {
tsym.kind.str() + '_'
} else {
tsym.kind.str()
}
return '(v__reflection__TypeSymbol){.name=_SLIT("${tsym.name}"),.idx=${tsym.idx},.parent_idx=${tsym.parent_idx},.language=_SLIT("${tsym.language}"),.kind=v__ast__Kind__${kind_name}}'
}
// gen_reflection_function generates C code for reflection function metadata
[inline]
fn (mut g Gen) gen_reflection_function(node ast.FnDecl) {
if !g.has_reflection {
return
}
func_struct := g.gen_reflection_fndecl(node)
g.reflection_funcs.write_string('\tv__reflection__add_func(${func_struct});\n')
}
// gen_reflection_data generates code to initilized V reflection metadata
fn (mut g Gen) gen_reflection_data() {
// modules declaration
for mod_name in g.table.modules {
g.reflection_others.write_string('\tv__reflection__add_module(_SLIT("${mod_name}"));\n')
}
// enum declaration
for full_name, enum_ in g.table.enum_decls {
name := full_name.all_after_last('.')
fields := g.gen_reflection_enum_fields(enum_.fields)
g.reflection_others.write_string('\tv__reflection__add_enum((v__reflection__Enum){.name=_SLIT("${name}"),.is_pub=${enum_.is_pub},.is_flag=${enum_.is_flag},.typ=${enum_.typ.idx()},.line_start=${enum_.pos.line_nr},.line_end=${enum_.pos.last_line},.fields=${fields}});\n')
}
// types declaration
for full_name, idx in g.table.type_idxs {
tsym := g.table.sym_by_idx(idx)
name := full_name.all_after_last('.')
sym := g.gen_reflection_sym(tsym)
g.reflection_others.write_string('\tv__reflection__add_type((v__reflection__Type){.name=_SLIT("${name}"),.idx=${idx},.sym=${sym}});\n')
}
// interface declaration
for _, idecl in g.table.interfaces {
name := idecl.name.all_after_last('.')
methods := g.gen_function_array(idecl.methods)
g.reflection_others.write_string('\tv__reflection__add_interface((v__reflection__Interface){.name=_SLIT("${name}"),.typ=${idecl.typ.idx()},.is_pub=${idecl.is_pub},.methods=${methods}});\n')
}
// type symbols declaration
for _, tsym in g.table.type_symbols {
sym := g.gen_reflection_sym(tsym)
g.reflection_others.write_string('\tv__reflection__add_type_symbol(${sym});\n')
}
g.gen_reflection_strings()
// funcs meta info filling
g.writeln(g.reflection_funcs.str())
// others meta info filling
g.writeln(g.reflection_others.str())
}

View File

@ -0,0 +1,194 @@
[has_globals]
module reflection
import v.ast
__global g_reflection = Reflection{}
[heap; minify]
pub struct Reflection {
pub mut:
modules []Module
funcs []Function
types []Type
type_symbols []TypeSymbol
enums []Enum
interfaces []Interface
strings map[int]string
}
pub struct Interface {
pub:
name string // interface name
typ int // type idx
is_pub bool // is pub?
methods []Function // methods
}
pub struct EnumField {
pub:
name string // field name
}
pub struct Enum {
pub:
name string // enum name
is_pub bool // is pub?
is_flag bool // is flag?
typ int // type idx
line_start int // decl start line
line_end int // decl end line
fields []EnumField // enum fields
}
pub struct TypeSymbol {
pub:
name string // symbol name
idx int // symbol idx
parent_idx int // symbol parent idx
language string // language
kind ast.Kind // kind
}
pub struct Type {
pub:
name string // type name
idx int // type idx
sym TypeSymbol // type symbol
}
pub struct Module {
pub:
name string // module name
}
pub struct FunctionArg {
pub:
name string // argument name
typ int // argument type idx
}
pub struct Function {
pub:
mod_name string // module name
name string // function/method name
args []FunctionArg // function/method args
file_idx int // source file name
line_start int // decl start line
line_end int // decl end line
is_variadic bool // is variadic?
return_typ int // return type idx
receiver_typ int // receiver type idx (is a method)
}
// API module
pub fn get_string_by_idx(idx int) string {
return g_reflection.strings[idx]
}
// type_of returns the type info of the passed value
pub fn type_of[T](val T) Type {
return g_reflection.types.filter(it.idx == typeof[T]().idx)[0]
}
// get_modules returns the module name built with V source
pub fn get_modules() []Module {
return g_reflection.modules
}
// get_functions returns the functions built with V source
pub fn get_funcs() []Function {
return g_reflection.funcs
}
// get_types returns the registered types
pub fn get_types() []Type {
return g_reflection.types
}
// get_enums returns the registered enums
pub fn get_enums() []Enum {
return g_reflection.enums
}
// get_aliases returns the registered aliases
pub fn get_aliases() []Type {
alias_idxs := g_reflection.type_symbols.filter(it.kind == .alias).map(it.idx)
return g_reflection.types.filter(it.idx in alias_idxs)
}
// get_interfaces returns the registered aliases
pub fn get_interfaces() []Interface {
return g_reflection.interfaces
}
// get_sum_types returns the registered sum types
pub fn get_sum_types() []Type {
sumtype_idxs := g_reflection.type_symbols.filter(it.kind == .sum_type).map(it.idx)
return g_reflection.types.filter(it.idx in sumtype_idxs)
}
// get_type_symbol returns the registered type symbols
pub fn get_type_symbols() []TypeSymbol {
return g_reflection.type_symbols
}
// Type API
pub fn type_name(idx int) string {
t := g_reflection.types.filter(it.idx == idx)
return if t.len != 0 { t[0].name } else { '' }
}
pub fn get_type(idx int) ?Type {
t := g_reflection.types.filter(it.idx == idx)
return if t.len != 0 { t[0] } else { none }
}
// Type Symbol API
pub fn type_symbol_name(idx int) string {
t := g_reflection.type_symbols.filter(it.idx == idx)
return if t.len != 0 { t[0].name } else { '' }
}
pub fn get_type_symbol(idx int) ?TypeSymbol {
t := g_reflection.type_symbols.filter(it.idx == idx)
return if t.len != 0 { t[0] } else { none }
}
// V reflection metainfo API (called from backend to fill metadata info)
[markused]
fn add_module(mod_name string) {
g_reflection.modules << Module{mod_name}
}
[markused]
fn add_func(func Function) {
g_reflection.funcs << func
}
[markused]
fn add_type(type_ Type) {
g_reflection.types << type_
}
[markused]
fn add_type_symbol(typesymbol TypeSymbol) {
g_reflection.type_symbols << typesymbol
}
[markused]
fn add_enum(enum_ Enum) {
g_reflection.enums << enum_
}
[markused]
fn add_interface(interface_ Interface) {
g_reflection.interfaces << interface_
}
[markused]
fn add_string(str string, idx int) {
g_reflection.strings[idx] = str
}

View File

@ -0,0 +1,88 @@
import v.reflection
type MyInt = int
type MySumType = f64 | int
enum TestEnum {
foo
bar
}
struct User {
name string
}
fn (u User) get_name() string {
return u.name
}
fn test2(arg []string) {}
[noreturn]
fn test3(a reflection.Function) {
}
fn test_module_existing() {
assert 'v.reflection' in reflection.get_modules().map(it.name)
}
fn test_func_attribute() {
assert reflection.get_funcs().filter(it.name == 'test3')[0].is_variadic == false
}
fn test_func_name() {
assert reflection.get_funcs().filter(it.name == 'test2')[0].name == 'test2'
}
fn test_type_name() {
ret_typ := reflection.get_funcs().filter(it.name == 'test3')[0].return_typ
assert reflection.type_name(ret_typ) == 'void'
assert reflection.get_type(ret_typ)?.name == 'void'
assert reflection.get_type_symbol(ret_typ)?.name == 'void'
assert reflection.type_name(reflection.get_funcs().filter(it.name == 'test3')[0].args[0].typ) == 'Function'
}
fn test_type_symbol() {
ret_typ := reflection.get_funcs().filter(it.name == 'test3')[0].return_typ
assert reflection.get_type_symbol(ret_typ)?.language == 'v'
}
fn test_method() {
method := reflection.get_funcs().filter(it.name == 'get_name')[0]
assert reflection.type_name(method.return_typ) == 'string'
println(reflection.get_type(method.receiver_typ)?.name)
assert reflection.get_type(method.receiver_typ)?.name == 'User'
}
fn test_enum() {
assert reflection.get_enums().filter(it.name == 'TestEnum')[0].name == 'TestEnum'
}
fn test_get_aliases() {
assert reflection.get_aliases().filter(it.name == 'MyInt')[0].name == 'MyInt'
}
fn test_get_sum_types() {
assert reflection.get_sum_types().filter(it.name == 'MySumType')[0].name == 'MySumType'
}
fn test_get_interfaces() {
assert reflection.get_interfaces().filter(it.name == 'IError')[0].name == 'IError'
}
fn test_interfaces() {
assert reflection.get_interfaces().filter(it.name == 'IError')[0].methods.len == 2
}
fn test_enum_fields() {
assert reflection.get_enums().filter(it.name == 'TestEnum')[0].fields.map(it.name) == [
'foo',
'bar',
]
}
fn test_get_string_by_idx() {
file_idx := reflection.get_funcs().filter(it.name == 'all_after_last')[0].file_idx
assert reflection.get_string_by_idx(file_idx).ends_with('string.v')
}