编译时反射

编译时就是编译器生成C代码时,只会生成指定平台的代码,其他平台的C代码不会生成.

有别于运行时代码,不管是那个平台的代码都会生成对应的C代码.

在V语言中所有的编译时代码都是以$开头的.

动态获取字段和方法

$for用来实现反射的效果,目前只实现了结构体的反射,可以在运行时获得某一个结构体所有字段和方法的信息

遍历结构体字段,返回字段信息数组:[]FieldData

  1. FieldData {
  2. name: 'a' //字段名称
  3. attrs: [] //字段注解
  4. is_pub: false //是否公共
  5. is_mut: false //是否可变
  6. typ: 18 //字段类型
  7. }

遍历结构体方法,返回方法信息数组:[]FunctionData

  1. FunctionData {
  2. name: 'int_method1' //方法名称
  3. attrs: [] //方法注解
  4. args: [] //方法参数
  5. return_type: 7 //方法返回类型
  6. typ: 0 //未知
  7. }
  1. struct App {
  2. a string
  3. b string
  4. mut:
  5. c int
  6. d f32
  7. pub:
  8. e f32
  9. f u64
  10. pub mut:
  11. g string
  12. h byte
  13. }
  14. ['foo/bar/three']
  15. fn (mut app App) run() {
  16. }
  17. ['attr2']
  18. fn (mut app App) method2() {
  19. }
  20. fn (mut app App) int_method1() int {
  21. return 0
  22. }
  23. fn (mut app App) int_method2() int {
  24. return 1
  25. }
  26. fn (mut app App) string_arg(x string) {
  27. }
  28. fn no_lines(s string) string {
  29. return s.replace('\n', ' ')
  30. }
  31. fn f1() {
  32. println(@FN)
  33. methods := ['run', 'method2', 'int_method1', 'int_method2', 'string_arg']
  34. $for method in App.methods { //遍历结构体所有方法
  35. println(' method: $method.name | ' + no_lines('$method'))
  36. assert method.name in methods
  37. }
  38. }
  39. fn f2() {
  40. println(@FN)
  41. $for method in App.methods { //遍历结构体所有方法
  42. println(' method: ' + no_lines('$method'))
  43. $if method.typ is fn () {
  44. assert method.name in ['run', 'method2']
  45. }
  46. $if method.return_type is int {
  47. assert method.name in ['int_method1', 'int_method2']
  48. }
  49. $if method.args[0].typ is string {
  50. assert method.name == 'string_arg'
  51. }
  52. }
  53. }
  54. fn main() {
  55. println(@FN)
  56. $for field in App.fields { //遍历结构体所有字段
  57. println(' field: $field.name | ' + no_lines('$field'))
  58. $if field.typ is string {
  59. assert field.name in ['a', 'b', 'g']
  60. }
  61. $if field.typ is f32 {
  62. assert field.name in ['d', 'e']
  63. }
  64. if field.is_mut {
  65. assert field.name in ['c', 'd', 'g', 'h']
  66. }
  67. if field.is_pub {
  68. assert field.name in ['e', 'f', 'g', 'h']
  69. }
  70. if field.is_pub && field.is_mut {
  71. assert field.name in ['g', 'h']
  72. }
  73. }
  74. f1()
  75. f2()
  76. }

动态字段赋值

  1. struct Foo {
  2. immutable int
  3. mut:
  4. test string
  5. name string
  6. }
  7. fn comptime_field_selector_read<T>() []string {
  8. mut t := T{}
  9. t.name = '2'
  10. t.test = '1'
  11. mut value_list := []string{}
  12. $for f in T.fields {
  13. $if f.typ is string {
  14. value_list << t.$f.name
  15. }
  16. }
  17. return value_list
  18. }
  19. fn test_comptime_field_selector_read() {
  20. assert comptime_field_selector_read<Foo>() == ['1', '2']
  21. }
  22. fn comptime_field_selector_write<T>() T {
  23. mut t := T{}
  24. $for f in T.fields {
  25. $if f.typ is string {
  26. t.$f.name = '1'
  27. }
  28. $if f.typ is int {
  29. t.$f.name = 1
  30. }
  31. }
  32. return t
  33. }
  34. fn test_comptime_field_selector_write() {
  35. res := comptime_field_selector_write<Foo>()
  36. assert res.immutable == 1
  37. assert res.test == '1'
  38. assert res.name == '1'
  39. }
  40. struct Foo2 {
  41. f Foo
  42. }
  43. fn nested_with_parentheses<T>() T {
  44. mut t := T{}
  45. $for f in T.fields {
  46. $if f.typ is Foo {
  47. t.$(f.name).test = '1'
  48. }
  49. }
  50. return t
  51. }
  52. fn test_nested_with_parentheses() {
  53. res := nested_with_parentheses<Foo2>()
  54. assert res.f.test == '1'
  55. }

动态调用方法

  1. struct TestStruct {}
  2. fn (t TestStruct) one_arg(a string) {
  3. println(a)
  4. }
  5. fn (t TestStruct) two_args(a string, b int) {
  6. println('$a:$b')
  7. }
  8. fn main() {
  9. t := TestStruct{}
  10. $for method in TestStruct.methods { // 获取结构体的所有方法
  11. if method.name == 'two_args' {
  12. t.$method('hello', 42) // 动态调用方法
  13. }
  14. }
  15. }
  1. struct MyStruct {}
  2. struct TestStruct {}
  3. fn (t TestStruct) three_args(m MyStruct, arg1 string, arg2 int) {
  4. println('$arg1,$arg2')
  5. }
  6. fn main() {
  7. t := TestStruct{}
  8. m := MyStruct{}
  9. args := ['one' '2'] //数组参数
  10. $for method in TestStruct.methods {
  11. if method.name == 'three_args' {
  12. t.$method(m, args) //动态调用方法时,也可以传递一个数组,会自动解构展开
  13. }
  14. }
  15. }

编译时预置的全局变量

  1. module main
  2. fn main() {
  3. println('module: ${@MOD}') //当前模块
  4. println('fn: ${@FN}') //当前函数
  5. println('sturct: ${@STRUCT}') //当前结构体
  6. println('method: ${@METHOD}') //当前方法
  7. println('vexe: ${@VEXE}') //当前V编译器命令行可执行文件
  8. println('vexeroot: ${@VEXEROOT}') //当前V编译器命令行所在的目录
  9. println('file: ${@FILE}') //当前源代码文件名
  10. println('line: ${@LINE}') //当前代码所在的行
  11. println('column: ${@COLUMN}') //当前代码在当前行中的列数
  12. println('vhash: ${@VHASH}') //当前V命令行编译时的hash
  13. println('vmod_file: ${@VMOD_FILE}') //当前文件所处项目的v.mod文件内容
  14. println('vmodroot: ${@VMODROOT}') //当前文件所处项目的v.mod文件所在的目录
  15. }

编译时获取环境变量

可以使用$env函数获取环境变量

运行时代码中os.get_env函数也可以实现相同的效果

比较特别的是,$env也可以在#flay和#include等C宏中使用,让C宏的定义更灵活

  1. module main
  2. //可以在C宏语句中使用,让C宏的定义更灵活
  3. #flag linux -I $env('JAVA_HOME')/include
  4. fn main() {
  5. compile_time_env := $env('PATH')
  6. println(compile_time_env)
  7. }

编译时嵌入文件

可以使用$embed_file编译时函数,把各种类型的文件在编译时嵌入到二进制文件中,更方便编译打包成单一可执行文件,方便部署,目前vweb框架中有使用到.

  1. module main
  2. import os
  3. fn main() {
  4. mut embedded_file := $embed_file('v.png')
  5. mut fw := os.create('exported.png') or { panic(err) }
  6. fw.write_bytes(embedded_file.data(), embedded_file.len)
  7. fw.close()
  8. }

编译模板

V内置了一个简单的txt和html模板,可以通过$tmpl编译时函数,进行渲染

  1. fn build() string {
  2. name := 'Peter'
  3. age := 25
  4. numbers := [1, 2, 3]
  5. return $tmpl('1.txt') //读取模板文件,然后把变量替换到模板中对应的同名变量
  6. }
  7. fn main() {
  8. println(build())
  9. }

1.txt模板文件内容

  1. name: @name //@开头的都是模板文件中需要替换的同名变量
  2. age: @age
  3. numbers: @numbers
  4. @for number in numbers //也可以遍历数组类型变量
  5. @number
  6. @end