V编译器源代码

V已实现了自举,所有编译器和标准库都是V开发的,在实现自举之前,使用golang作为早期编译器的开发。

V编译器基于AST(抽象语法树)的方式来编译,源代码先生成AST,再生成目标代码(c/js/native/interpreter),是目前各语言编译器的主流方式,好处很多:

  • 编译器代码更清晰,更好维护

  • 编译器再增加新东西,速度会更快

  • AST可以更快速地生成其他目标代码

  • AST可以给开发环境以及编译工具使用,更快开发对应的插件和工具

同时V编译器代码也是V标准库vlib的一部分,位于vlib/v目录中,这样的设计可以很方便地调用V编译器本身的各种功能模块,开发各种编译工具链。

代码目录

cmd - V命令行代码目录

子目录说明

cmd/v

V编译器命令行,入口文件v.v,编译成V可执行文件

cmd/v/help

V编译器命令行的帮助文本

cmd/tools

各种工具类,如vup/vfmt/vpm/vrepl/vtest/vcreate等

vlib/v - V编译器源代码目录

子目录说明

ast

AST抽象语法树和全局符号表

token

词法单元相关

pref

编译选项/参数相关

scanner

词法分析/扫描器相关

parser

语法分析/解析器相关

checker

语法检查器相关

eval

解释器相关

depgraph

模块依赖相关

builder

代码生成器相关

gen

生成具体平台代码相关,如生成C/js/native

fmt

代码格式化相关

doc

代码文档生成相关

tests

编译器测试代码

errors

编译器报错和警告

vmod

解析v.mod文件

cflag

解析cflag标识

vcache

编译器缓存相关

vet

小工具,用于分析项目代码存在的错误

pkgconfig

小工具,用于配置C代码的编译标记(C flags)

util

编译器各种辅助类

embed_file

将各种文件嵌入到可执行文件中,形成单一可执行文件

callgraph

显示函数调用图,生成Graphviz的dot格式

preludes

preludes_js

live

markused

transformer

thirdparty - 第三方C代码依赖库

子目录说明

cJSON

fontstash

ios

ios依赖库

libgc

mssql

mssql头文件库

sokol

picoev

picohttpparser

stb_image

stdatomic

C的原子操作库

tcc

vschannel

zip

C的zip压缩库

主要编译过程

  • V命令行的入口文件是v/cmd/v/v.v

  • V命令行负责处理命令参数,创建编译器参数对象(pref.Preferences)

  • V命令行根据参数调用builder.compiler()开始编译,或调用tools目录中的各种工具,比如vfmt代码格式化工具

  • V编译器对象根据编译参数,分析得到所有需要编译的源文件数组[ ]os.File,源文件列表也包括了vlib/builtin中的内置源文件

  • 通过调用builder.compile()方法,开始编译,首先创建代码生成器对象(builder.Builder)

  • 通过调用代码生成器对象的b.build_c()方法,把源代码文件数组[]File中的所有V源代码文件生成C源文件

  • 一个源代码文件对应一个语法分析器,一个语法分析器对应一个词法扫描器

  • 代码生成器对象负责创建语法分析器对象(parser.Parser),启动语法分析

  • 语法分析器负责创建词法扫描器对象(scanner.Scanner),启动词法扫描

  • 语法分析和词法扫描实际上是同步进行,同步完成的.语法分析器对[ ]os.File进行遍历,以词法单元(token)为基本单位,进行语法分析,词法单元由词法扫描器负责扫描,识别,提供给语法分析

  • 语法分析遍历[ ]os.File的所有源文件后,生成对应的文件语法树对象数组[ ]ast.File

  • 语法分析器分析完[ ]File源文件后,接着对所有导入的依赖,再次进行分析,将分析结果追加到文件语法树对象数组([ ]ast.File)中

  • 语法分析器对象分析完成后,输出[ ]ast.File给代码生成器对象,由代码生成器对象,调用指定平台的代码生成对象(gen.Gen/gen.JsGen/gen.x64Gen)来生成目标代码,目前主要是调用gen.Gen来生成C源文件

  • 最后调用builder.cc方法,调用编译器参数指定的C编译器,将C源文件编译生成可执行文件,完成整个编译过程

简单总结整个编译过程就是: [ ]os.File => [ ]ast.File => 目标代码(c/x64/js) => 可执行文件

编译器代码量

V实现了自举,整个V编译器都是由V语言开发的,编译器加上标准库的代码量大约为15W多行代码,持续增加中.

使用scc代码统计工具的统计数据如下:

编译速度监控

V专门开发了一个工具,用来监控每一次V代码库提交,对编译速度的影响。

详细记录可以参考:https://fast.vlang.io/

编译器类

以下类图是V编译器中主要的类和枚举:

主要的调用关系是:cmd => builder.Builder => parser.Parser => scanner.Scanner

类和枚举说明:

  • pref.Preferences

    V编译器选项类,保存着所有的编译器选项和参数

    参数太多,不一一展开,列举几个主要的:

    字段说明

    build_mode

    编译模式

    is_script

    是否vscript脚本

    is_prof

    是否生产模式编译

    is_run

    是否v run执行

    ....

  • builder.Builder

    代码生成器类,由V编译器负责创建

    字段/方法说明

    pref

    对编译选项对象的引用

    tables

    对符号表对象的引用

    checker

    对代码检查器对象的引用

    parsed_files

    语法分析器生成的文件语法树对象数组

    module_search_paths

    模块搜索路径

    out_name_c

    保存需要编译生成的C文件名

    out_name_js

    保存需要编译生成的js文件名

    ...

    compile()

    负责启动编译

    build_c()

    负责编译生成C源代码

    build_js()

    负责编译生成js源代码

    build_x64()

    负责编译生成x64机器码

    cc()

    将C源代码编译生成可执行文件

    ...

  • parser.Parser

    语法分析器,由代码生成器负责创建

    字段/方法说明

    scanner

    保存对应的词法扫描器引用

    file_name

    保存对应的源文件

    tables

    对符号表对象的引用

    pref

    对编译选项对象的引用

    其他字段都是语法分析中的过程变量:

    pos

    当前token

    peek_tok

    下一个token

    scope &ast.Scope

    当前作用域

    ...

    parse_file(path string,table &table.Table)

    对一个源文件进行语法分析

    parse_files(paths []string)

    对一组源文件进行语法分析

    ...

  • ast.table

    符号表,保存着语法分析后,得到的所有变量,函数,类型等

    字段/方法说明

    types []TypeSymbol

    所有类型

    local_vars

    所有类型索引

    fns map[string]Fn

    所有函数和方法

    consts map[string]Var

    所有常量

    imports []string

    所有导入模块

    modules []string

    所有模块

    register_const()

    注册常量到符号表

    register_global()

    注册全局变量到符号表

    register_var()

    注册变量到符号表

    register_fn()

    注册函数到符号表

    register_method()

    注册方法到符号表

    register_type()

    注册类型到符号表

    ...

  • scanner.Scanner

    词法扫描器,由语法分析器负责创建,对源文件进行逐个字节进行扫描,识别出token

    字段/方法说明

    file_path

    保存扫描的当前源文件路径

    text

    保存文件所有内容的字符串

    其他字段全是扫描的过程变量:

    pos

    扫描到的当前字节位置

    line_nr

    扫描到的行

    ...

    scan()

    启动一次扫描,返回一个token,由语法分析器调用

    scan_res()

    返回一次扫描结果

    ...

    根据指定的源文件,创建扫描器

    ...

  • token.Token

    词法单元类,表示词法扫描器识别出来的一个token

    字段/方法说明

    kind

    token的种类

    lit

    string 字面量值,只有种类为name,number,str,str_inter,chartoken时才会有值

    line_nr

    行位置

    pos

    列位置

    position()

    返回当前token的位置,行位置和列位置

  • token.Kind

    词法单元种类枚举,一共有139个可以被扫描器识别的词法单元

    枚举值说明

    eof

    表示文件结束

    name

    标识符,tokon的lit字段有具体值

    number

    数字,tokon的lit字段有具体值

    str

    字符串,tokon的lit字段有具体值

    chartoken

    单字符,tokon的lit字段有具体值

    plus

    +加

    minus

    -减

    mul

    *乘

    div

    /除

    mod

    %余

    xor

    ^异或

    bit_not

    ~位取反

    pipe

    |管道符

    hash

    # 用于C宏

    amp

    &位且,也用于变量取地址

    inc

    ++递增

    dec

    --递减

    and

    &&逻辑且

    logical_or

    ||逻辑或

    not

    !逻辑非

    dot

    .点运算符

    dotdot

    ..运算符,用于数组区间

    ellipsis

    ...展开符,用于函数不确定参数

    comma

    ,逗号

    semicolon

    ;分号

    colon

    :冒号,用于字典初始化,结构体初始化,结构体字段访问控制,goto标签

    arrow

    =>箭头

    assign

    =变量赋值/分配

    decl_assign

    :=变量声明

    plus_assign

    +=加等于

    minus_assign

    -=减等于

    mult_assign

    *=乘等于

    div_assign

    /=除等于

    xor_assign

    ^=异或等于

    mod_assign

    %=余等于

    or_assign

    |=位或等于

    and_assign

    &=位且等于

    righ_shift_assign

    >>=位右移等于

    left_shift_assign

    <<=位左移等于

    lcbr

    {左大括号

    rcbr

    }右大括号

    lpar

    (左小括号

    rpar

    )右小括号

    lsbr

    [左中括号

    rsbr

    ]右中括号

    eq

    ==相等

    ne

    ≠不等于

    gt

    >大于

    lt

    <小于

    ge

    >=大于等于

    le

    <=小于等于

    question

    ?问号

    left_shift

    <<位左移

    right_shift

    >>位右移

    line_comment

    //单行注释

    mline_comment

    /*多行注释开头

    nl

    NLL空值字符

    dollar

    $美金符号

    str_dollar

    $字符串内的美金符号

    keyword_beg

    表示在keyword_beg和keyword_end之间的都是关键字,本身无意义

    key_assert

    关键字assert

    key_struct

    关键字struct

    key_if

    关键字if

    key_else

    关键字else

    key_asm

    关键字asm

    key_return

    关键字return

    key_module

    关键字module

    key_sizeof

    关键字sizeof

    key_go

    关键字go

    key_goto

    关键字goto

    key_const

    关键字const

    key_var

    关键字var

    key_type

    关键字type

    key_for

    关键字for

    key_switch

    关键字switch

    key_fn

    关键字fn

    key_true

    关键字true

    key_false

    关键字false

    key_continue

    关键字continue

    key_break

    关键字break

    key_import

    关键字import

    key_embed

    关键字embed,未使用

    key_unsafe

    关键字unsafe

    key_typeof

    关键字typeof

    key_enum

    关键字enum

    key_interface

    关键字interface

    key_pub

    关键字pub

    key_import_const

    关键字import_const,用于导入C常量

    key_in

    关键字in

    key_atomic

    关键字atomic,未使用

    key_orelse

    关键字or

    key_global

    关键字__global,全局变量

    key_union

    关键字union

    key_static

    关键字static,用于C函数的static

    key_as

    关键字as

    key_defer

    关键字defer

    key_match

    关键字match

    key_select

    关键字select,用于db.select

    key_none

    关键字none

    key_offsetof

    关键字__offsetof,未使用

    keyword_end

    表示在keyword_beg和keyword_end之间的都是关键字,本身无意义

  • ast.Fn

    函数类型

    字段/方法说明

    name string

    函数名

    args []Var

    函数的参数

    return_type Type

    函数的返回类型

    is_variadic bool

    是不是可变参数函数

  • ast.Var

    变量和常量类型

    字段/方法说明

    name string

    变量名

    idx int

    变量在符号表中的索引

    is_mut bool

    是否可变

    is_const bool

    是否常量

    is_global bool

    是否全局变量

    scope_level int

    作用域级别

    typ Type

    变量类型

  • ast.Field

    结构体字段类型

    字段/方法说明

    name string

    字段名称

    typ Type

    字段类型

  • ast.Scope

    作用域

    字段/方法说明

    parent &Scope

    父作用域

    children []&Scope

    子作用域

    start_pos int

    作用域开始位置

    end_pos int

    作用域结束位置

  • ast.TypeSymbol

    类型,描述类型的类型

    字段/方法说明

    parent &TypeSymbol

    类型的父类型

    kind Kind

    类型的种类

    info TypeInfo

    类型的额外信息类,只对6个类型有用

    name string

    类型的名字

  • ast.Type

    类型引用,i32的类型别名,表示每一个类型在符号表中的唯一ID

    这个i32的ID有进行位移处理,前16位保存着类型的ID信息,后16保存着类型的指针引用层级信息

    ​ 原来是设计为一个TypeRef结构体的,因为在符号表等很多地方都被引用,所以把ID和指针引用层级信息合并成一个int32,性能更好,占用内存更小.

    pub type Type int
  • ast.AccessMod

    结构体字段的访问控制枚举

    字段/方法说明

    private

    模块私有,且只读

    private_mut

    模块私有,但可变

    public

    所有模块可访问,但只读

    public_mut

    所有模块可访问,且可变

    global

    全局字段,所有模块可访问,且可修改,不推荐使用

  • ast.Kind

    类型的种类,包含了V语言中的所有类型种类,包括基本类型,内置类型,结构体类型等其他种类,对类型进行分类以后,就可以针对不同的种类进行不同的语法分析,语法检查

    字段/方法说明

    placeholder

    占位种类

    void

    voidptr

    通用指针类型

    &char

    C字符指针类型

    &u8

    字节指针

    i8

    i16

    int

    i64

    u16

    u32

    u64

    f32

    f64

    string

    字符串

    char

    单字符

    u8

    bool

    const_

    常量

    enum_

    枚举

    struct_

    结构体

    array

    数组

    array_fixed

    固定大小数组

    map

    字典

    multi_return

    多返回值类型

    sum_type

    联合类型

    alias

    类型别名

    unresolved

  • ast.TypeInfo

    类型信息类,该类型是一个联合类型,作为几种特殊类型的额外信息

    pub type TypeInfo = Array | ArrayFixed | Map | Struct | 	
    MultiReturn | Alias
  • ast.Struct

    结构体类型,TypeInfo联合类型的其中一种类型

    作为类型信息类的其中一种类型,要描述一个完整的结构体类型所包含的信息,就是TypeSymbol中的基本字段,再加上这个结构体中的字段

    字段/方法说明

    fields []Field

    结构体的所有字段

  • ast.Array

    数组类型,TypeInfo联合类型的其中一种类型

    字段/方法说明

    elem_type Type

    数组类型

    nr_dims int

    数组维度

  • ast.ArrayFixed

    固定大小数组类型,TypeInfo联合类型的其中一种类型

    字段/方法说明

    elem_type Type

    数组类型

    nr_dims int

    数组维度

    size int

    数组大小

  • ast.Map

    字典类型,TypeInfo联合类型的其中一种类型

    字段/方法说明

    key_type Type

    键的类型

    value_type Type

    值的类型

  • ast.MultiReturn

    多返回值类型,TypeInfo联合类型的其中一种类型

    V语言的多返回值实际上是返回一个结构体,动态生成的结构体,就是用这个来定义

    字段/方法说明

    name string

    类型名字

    types []Type

    包含多返回值的类型数组

  • ast.Alias

    类型别名,TypeInfo联合类型的其中一种类型

    字段/方法说明

    foo string

    类型别名

  • check.Checker

    代码检查器,用于检查各种语法的合法性

    字段/方法说明

    table &table.Table

    符号表的引用

    file ast.File

    要检查的文件语法树

    nr_errors int

    错误个数

    errors []string

    错误内容数组

    check_files()

    检查多个文件语法树的语法合法性

    check()

    检查单个文件语法树

    stmt()

    检查语句,递归检查

    expr()

    检查表达式,递归检查

    error()

    没有通过检查项,报错

    check_struct_init

    检查结构体初始化的合法性

    check_assign_expr

    检查变量赋值的合法性

    call_expr

    检查函数调用

    check_method_call_expr

    检查方法调用

    return_stmt

    检查返回语句

    array_init

    检查数组初始化

    ...

  • gen.Gen

    C源代码生成器

    字段/方法说明

    out strings.Builder

    生成的C源代码主体,保存在这个字符串中,字符串生成器对象

    definitions strings.Builder

    生成的C源代码宏部分,保存在这个字符串中,字符串生成器对象

    完整的C源代码由这两个部分组成:definitions+out

    table &table.Table

    符号表的引用

    fn_decl &ast.FnDecl

    过程变量,生成过程中的当前函数

    tmp_count int

    过程变量,用于生成临时变量的计数,每一个函数重置

    cgen() string

    生成所有[ ]ast.File的C源代码

    stmts()

    遍历stmts生成所有的C代码

    stmt()

    根据一个stmt语句,生成对应的C代码段

    write() string

    生成C代码段,不换行

    writeln() string

    生成C代码段,换行

  • gen.JsGen

    js源代码生成器

    字段/方法说明

    out strings.Builder

    生成的js源代码,保存在这个字符串中,字符串生成器对象

    table &table.Table

    符号表的引用

  • gen.x64.Gen

    x64机器码生成器

    字段/方法说明

    out_name string

    生成的机器代码,保存在这个字符串中,字符串生成器对象

    buf []u8

    sect_header_name_pos int

    offset i64

    str_pos []i64

    strings []string

    file_size_pos i64

    main_fn_addr i64

    code_start_pos i64

    fn_addr map[string]i64

    gen()

    生成x64代码

  • fmt.Fmt

    代码格式化器,根据文件语法树,生成格式工整的代码,然后再覆盖回未格式化的代码

    字段/方法说明

    out strings.Builder

    格式化后的代码字符串

    table &table.Table

    符号表的引用

    indent int

    格式化过程变量,记录当前代码缩进的等级

    empty_line bool

    格式化过程变量,记录当前行是否是空行,如果是,要先生成缩进

    line_len int

    格式化过程变量,记录当前行目前的长度,为了处理80换行

    single_line_if bool

    格式化过程变量,判断是否单行if,为了格式化成1行

    cur_mod string

    格式化过程变量,记录当前模块

    fmt()

    格式化代码某个文件

    wrtie()

    往out字符串写入代码

    writeln()

    往out字符串写入代码,并换行

    stmts()

    格式化多个语句

    stmt()

    格式化单个语句

AST语法树类

源代码文件经过词法扫描,语法分析后的输出就是文件语法树对象数组([ ]ast.File)

以下类图就是文件语法树中每一个节点对应的类型

类型中有2个基本的类型:Stmt语句类型和Expr表达式类型,这两个类型是联合类型,联合类型的含义就是这种类型可以是图中箭头关联的子类型的其中一种,有点像继承关系中基类的作用,但又不是

而且Expr表达式类型通过ExprStmt表达式语句类型的关联,也是Stmt语句类型的子类,这样一来,所有的语法树节点都是Stmt语句类型的子类

文件语法树中以ast.File为根节点,节点包含子节点,形成一棵完整的语法树

联合类型Stmt和Expr,对应的V源代码:

pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl

pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral | CTempVar |
	CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal |
	FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral |
	Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr |
	RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
	StructInit | Type | TypeOf | UnsafeExpr

pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
	EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
	GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | SqlStmt |
	StructDecl | TypeDecl

主要类别说明:

  • ast.File

    文件语法树类,保存了一个v源文件的整棵语法树

字段/方法说明

path string

对应的v源文件路径

mod Module

模块节点

imports []Import

导入模块节点

stmts []Stmt

语句数组

scope &Scope

文件作用域

  • ast.Scope

    作用域节点,整个ast.File里也是保存了一棵作用域树,以ast.File的scope为根节点

字段/方法说明

parent &Scope

父作用域

children []&Scope

子作用域数组

start_pos int

作用域开始位置

end_pos int

作用域结束位置

vars map[string]VarDecl

该作用域内,声明的变量字典

register_var()

注册声明的变量

find_var()

在作用域内查找已声明的变量

override_var()

覆盖变量

作用域示意图:

  • 其他语法树节点,不详细展开,参考类图

可以使用vast这个工具,从V源文件生成AST json文件,这样就可以直观地看到AST:

https://github.com/lydiandy/vast

以下仅是举例,生成的语法树JSON方式表示如下:

/main.v生成的语法树为例
{
  "path": "path/to/main.v",
  "mod": {
    //Module类型
    "name": "main",
    "path": "path/to/main.v",
    "expr": {}
  },
  "imports": [
    //[]Import类型
    { "mod": "os", "alias": "", "pos": { "line_nr": 2 } },
    { "mod": "strings", "alias": "", "pos": { "line_nr": 3 } },
    { "mod": "time", "alias": "", "pos": { "line_nr": 4 } }
  ],
  "stmts": [
    //[]Stmt联合类型
    {
      //ConstDecl常量声明
      "fields": [
        {
          "name": "pi",
          "typ": { "idx": 12, "type": "0x13244344", "nr_muls": 0 }
        },
        {
          "name": "my_const",
          "typ": { "idx": 12, "type": "0x13244344", "nr_muls": 0 }
        },
        {
          "name": "my_int_const",
          "typ": { "idx": 12, "type": "0x13244344", "nr_muls": 0 }
        }
      ],
      "exprs": [
        //[]Expr联合类型
        {
          //FloatLiteral小数字面量类型
          "val": "3.14"
        },
        {
          //StringLiteral字符串字面量类型
          "val": "my value"
        },
        {
          //IntegerLiteral整数字面量类型
          "val": 10
        }
      ]
    },
    {
      //FnDecl函数声明
      "name": "main",
      "is_pub": "true",
      "is_variadic": "false",
      "receiver": {},
      "typ": { "idx": 12, "type": "0x13244344", "nr_muls": 0 },
      "args": [
        {
          "name": "a",
          "typ": { "idx": 12, "type": "0x13244344", "nr_muls": 0 }
        },
        {
          "name": "b",
          "typ": { "idx": 12, "type": "0x13244344", "nr_muls": 0 }
        }
      ],
      "stmts": [
        //函数体语句数组,一直往下嵌套语句,就是一棵函数体的语法树
        //[]Stmt联合类型,函数体的语句

        {
          //VarDecl变量声明
          "name": "x",
          "is_mut": "true",
          "type": { "idx": 12, "type": "0x13244344", "nr_muls": 0 },
          "expr": {
            //Expr联合类型,这里是StringLiteral字符串字面量类型
            "val": "abc"
          },
          "pos": { "line_nr": "4" }
        },
        {
          //IfExpr条件语句
        }
      ]
    }
  ]
}

词法扫描 scanner

以下是扫描器扫描识别token的逻辑:

  • s.text: 字符串类型,就是源代码的字符串表示

  • s.pos: 扫描的当前字节位置

  • s.scan()函数: 每调用一次,就接着s.pos当前位置,继续往下扫描,识别到一个token后返回

以下是扫描器碰到每一个字节时,进行扫描和识别的顺序:

当前字符识别内容继续识别

字符值为9/10/13/32

优先识别tab制表符/换行/回车/空格,跳过这些空格

字母/下划线

name名字

keyword关键字

数字/点加数字

number数字

0b 二进制数字

0x十六进制数字

0八进制数字

1-9/0.十进制数字

)

)

+

++

+=

+

-

--

-=

-

*

*=

*

^

^=

^

%

%=

%

?

?

'或"

string字符串

`

char单字符

(

(

)

)

[

[

]

]

{

{

字符串内递归识别

$

$

字符串内递归识别

}

}

字符串内递归识别

|

|=

||

,

,

@

FN

FILE

LINE

COLUMN

VHASH

@keyword

name名字

.

...

..

.

#

#!

#宏定义

>

>=

>>

>

0xE2

<

<=

<<

<

=

==

=>

=

:

:=

:

;

;

!

!=

!

~

~

/

/=

//

/*

\0

.eof windows end of file

以上都判断完了

如果还有不能识别的,就是invalid character

s.pos≥s.text.len

.eof 就是文件结束end of file


###语法分析 parser

语法分析器从p.parse_file()或者p.parse_files()开始启动

调用p.read_first_token()进行初始化后,p.tok和p.peek_tok就位,从第一个token开始分析

  • p.tok: 当前token

  • p.peek_tok: 下一个token

  • p.next(): 每调用一次,就调用一次扫描器的scan(),返回一个token,并将p.tok向后推进一个

  • p.check(token):

    ​ 检查p.tok是否为指定的token

    ​ 如果是就调用p.next(),将p.tok向后推进一个

    ​ 如果不是就报语法错误

  • p.check_name() string:

    ​ 检查p.tok是否为.name(标识符)的token

    ​ 如果是就返回tok.lit(具体的标识名),并调用p.next(),将p.tok向后推进一个

    ​ 如果不是就报语法错误

以下是语法分析的分析顺序:

当前token下个token识别内容(ast中的节点类型)说明

module

Module

识别模块定义

import

[ ]Import

识别导入语句

以下开始

识别所有的

顶级节点

遍历识别后,形成[ ]stmts

pub

识别以下公共的顶级节点

const

ConstDecl

识别一组常量声明

fn

FnDecl

识别函数声明或方法声明

struct

StructDecl

识别结构体声明

union

识别C联合类型

interface

识别接口声明

enum

EnumDecl

识别枚举声明

type

TypeDecl

识别类型别名/联合类型

[

Attr

识别函数注解

__global

GlobalDecl

识别全局变量

const

ConstDecl

识别一组常量声明

fn

FnDecl

识别函数声明或方法声明

struct

StructDecl

识别结构体声明

$

CompIf

识别条件编译语句

#

HashStmt

识别C宏

type

TypeDecl

识别类型别名/联合类型

enum

EnumDecl

识别枚举声明

//

Comment

识别单行注释

如果还存在以上都不是的顶级节点, 就报不合法的顶级节点错误


以下为顶级节点中包含的各种下级token的语法分析:

当前token下个token识别内容(ast中的节点类型)说明

[ ]stmts

StmtBlock代码块语句

通过p.stmt()的递归调用进行分析

mut

VarDecl

识别变量声明语句

for

ForCStmt/ForStmt/ForInStmt

识别3种for循环语句

return

Return

识别函数返回语句

$

CompIf

识别条件编译语句

continue

BranchStmt

识别continue语句

break

BranchStmt

识别break语句

unsafe

识别unsafe代码块语句(未实现)

defer

DeferStmt

识别defer代码块语句

goto

GotoStmt

识别goto代码块语句

.name

:

GotoLabel

识别为goto标签语句

.name

:

AssignStmt

识别为分配语句

=

Expr,识别表达式的值和类型

识别表达式,最复杂的

.name

继续识别出现名字的各种情况

.str

StringLiteral, table.string_type

识别字符串 x='abc'

.dot

EnumVal, table.int_type

识别枚举值 x=.blue

.chartoken

CharLiteral, table.u8_type

识别单字符 x=c

.key_true, .key_false

BoolLiteral,table.bool_type

识别布尔类型 x=true

.minus, .amp, .mul, .not, .bit_not

PrefixExpr

识别前缀表达式

.key_match

MatchExpr

识别match赋值语句

.number

IntegerLiteral/FloatLiteral

识别数值

.lpar

继续递归识别表达式

.key_if

IfExpr

识别if赋值语句

.lsbr

ArrayInit

识别数组初始化语句

.key_none

None, table.none_type

识别none类型

.key_sizeof

SizeOf,table.int_type

识别sizeof()函数

.lcbr

继续识别各种情况

如果以上都不是

报错:无效表达式

语法检查 checker

代码生成 gen

代码格式化 fmt

代码格式化的基本处理逻辑是: V代码(未格式化) => AST => V代码(格式化)

可以对文件或目录(包括子目录)进行代码格式化

格式化选项:

  • -w 最常用的选项,将格式化后的代码写入到源文件中,如果没有-w,默认只是输出到stdout

  • -l 列出所有需要格式化的文件名

  • -c 检查文件是否已经格式化过,如果文件未格式化,则输出文件路径,并且命令行退出码为2

  • -verify 跟-c基本一样,检查文件是否已经格式化过,速度慢一些,更准确一些,ci一般都用这个

  • -diff 调用系统默认的文件比较工具diff,比较文件格式化和未格式化之间的差异

  • -debug 输出格式化文件中所有的AST结构体,没啥用处

涉及的环境变量:

VDIFF_TOOL

如果指定了该环境变量,v fmt -diff不再使用系统默认的比较工具diff,而是使用指定的比较工具。

VDIFF_OPTIONS

传递给VDIFF_TOOL比较工具的选项。

最后更新于