v.ast: add walker submodule for ast walking (#7775)

pull/7980/head
Ned Palacios 2021-01-09 12:36:38 +08:00 committed by GitHub
parent eff757d0a1
commit 256ddcee1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 381 additions and 51 deletions

View File

@ -25,6 +25,11 @@ pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDe
// the .position() token.Position methods too.
pub type ScopeObject = ConstField | GlobalField | Var
// TOOD: replace table.Param
pub type Node = ConstField | EnumField | Expr | Field | File | GlobalField | IfBranch |
MatchBranch | ScopeObject | SelectBranch | Stmt | StructField | StructInitField | table.Param
pub struct Type {
pub:
typ table.Type
@ -134,6 +139,7 @@ pub:
name string
attrs []table.Attr
pos token.Position
name_pos token.Position // `name` in import name
is_skipped bool // module main can be skipped in single file programs
}
@ -266,11 +272,13 @@ pub mut:
// import statement
pub struct Import {
pub:
mod string // the module name of the import
alias string // the `x` in `import xxx as x`
pos token.Position
mod string // the module name of the import
alias string // the `x` in `import xxx as x`
pos token.Position
mod_pos token.Position
alias_pos token.Position
pub mut:
syms []ImportSymbol // the list of symbols in `import {symbol1, symbol2}`
syms []ImportSymbol // the list of symbols in `import {symbol1, symbol2}`
}
// import symbol,for import {symbol} syntax
@ -1257,6 +1265,180 @@ pub fn (stmt Stmt) position() token.Position {
}
}
pub fn (node Node) position() token.Position {
match node {
Stmt {
mut pos := node.position()
if node is Import {
for sym in node.syms {
pos = pos.extend(sym.pos)
}
}
return pos
}
Expr {
return node.position()
}
StructField {
return node.pos.extend(node.type_pos)
}
MatchBranch, SelectBranch, Field, EnumField, ConstField, StructInitField, GlobalField, table.Param {
return node.pos
}
IfBranch {
return node.pos.extend(node.body_pos)
}
ScopeObject {
match node {
ConstField, GlobalField, Var { return node.pos }
}
}
File {
mut pos := token.Position{}
if node.stmts.len > 0 {
first_pos := node.stmts.first().position()
last_pos := node.stmts.last().position()
pos = first_pos.extend_with_last_line(last_pos, last_pos.line_nr)
}
return pos
}
}
}
pub fn (node Node) children() []Node {
mut children := []Node{}
if node is Expr {
match node {
StringInterLiteral, Assoc, ArrayInit {
return node.exprs.map(Node(it))
}
SelectorExpr, PostfixExpr, UnsafeExpr, AsCast, ParExpr, IfGuardExpr, SizeOf, Likely, TypeOf, ArrayDecompose {
children << node.expr
}
LockExpr, OrExpr {
return node.stmts.map(Node(it))
}
StructInit {
return node.fields.map(Node(it))
}
AnonFn {
children << Stmt(node.decl)
}
CallExpr {
children << node.left
children << Expr(node.or_block)
}
InfixExpr {
children << node.left
children << node.right
}
PrefixExpr {
children << node.right
}
IndexExpr {
children << node.left
children << node.index
}
IfExpr {
children << node.left
children << node.branches.map(Node(it))
}
MatchExpr {
children << node.cond
children << node.branches.map(Node(it))
}
SelectExpr {
return node.branches.map(Node(it))
}
ChanInit {
children << node.cap_expr
}
MapInit {
children << node.keys.map(Node(it))
children << node.vals.map(Node(it))
}
RangeExpr {
children << node.low
children << node.high
}
CastExpr {
children << node.expr
children << node.arg
}
ConcatExpr {
return node.vals.map(Node(it))
}
ComptimeCall, ComptimeSelector {
children << node.left
}
else {}
}
} else if node is Stmt {
match node {
Block, DeferStmt, ForCStmt, ForInStmt, ForStmt, CompFor {
return node.stmts.map(Node(it))
}
ExprStmt, AssertStmt {
children << node.expr
}
InterfaceDecl {
return node.methods.map(Node(Stmt(it)))
}
AssignStmt {
children << node.left.map(Node(it))
children << node.right.map(Node(it))
}
Return {
return node.exprs.map(Node(it))
}
// NB: these four decl nodes cannot be merged as one branch
StructDecl {
return node.fields.map(Node(it))
}
GlobalDecl {
return node.fields.map(Node(it))
}
ConstDecl {
return node.fields.map(Node(it))
}
EnumDecl {
return node.fields.map(Node(it))
}
FnDecl {
if node.is_method {
children << Node(node.receiver)
}
children << node.params.map(Node(it))
children << node.stmts.map(Node(it))
}
else {}
}
} else if node is ScopeObject {
match node {
GlobalField, ConstField, Var { children << node.expr }
}
} else {
match node {
GlobalField, ConstField, EnumField, StructInitField {
children << node.expr
}
SelectBranch {
children << node.stmt
children << node.stmts.map(Node(it))
}
IfBranch, File {
return node.stmts.map(Node(it))
}
MatchBranch {
children << node.stmts.map(Node(it))
children << node.exprs.map(Node(it))
}
else {}
}
}
return children
}
// TODO: remove this fugly hack :-|
// fe2ex/1 and ex2fe/1 are used to convert back and forth from
// table.FExpr to ast.Expr , which in turn is needed to break

View File

@ -0,0 +1,37 @@
module walker
import v.ast
// Visitor defines a visit method which is invoked by the walker in each node it encounters.
pub interface Visitor {
visit(node ast.Node) ?
}
pub type InspectorFn = fn (node ast.Node, data voidptr) bool
struct Inspector {
inspector_callback InspectorFn
mut:
data voidptr
}
pub fn (i &Inspector) visit(node ast.Node) ? {
if i.inspector_callback(node, i.data) {
return
}
return none
}
// inspect traverses and checks the AST node on a depth-first order and based on the data given
pub fn inspect(node ast.Node, data voidptr, inspector_callback InspectorFn) {
walk(Inspector{inspector_callback, data}, node)
}
// walk traverses the AST using the given visitor
pub fn walk(visitor Visitor, node ast.Node) {
visitor.visit(node) or { return }
children := node.children()
for child_node in children {
walk(visitor, &child_node)
}
}

View File

@ -0,0 +1,71 @@
import v.ast
import v.ast.walker
import v.parser
import v.table
import v.pref
fn parse_text(text string) ast.File {
tbl := table.new_table()
prefs := pref.new_preferences()
scope := &ast.Scope{
parent: 0
}
return parser.parse_text(text, '', tbl, .skip_comments, prefs, scope)
}
struct NodeByOffset {
pos int
mut:
node ast.Node
}
fn (mut n NodeByOffset) visit(node ast.Node) ? {
node_pos := node.position()
if n.pos >= node_pos.pos && n.pos <= node_pos.pos + node_pos.len && node !is ast.File {
n.node = node
return none
}
return
}
fn test_walk() {
source := '
module main
struct Foo {
name string
}
'
file := parse_text(source)
mut nbo := NodeByOffset{
pos: 13
}
walker.walk(nbo, file)
assert nbo.node is ast.Stmt
stmt := nbo.node as ast.Stmt
assert stmt is ast.StructDecl
}
fn test_inspect() {
source := '
module main
'
file := parse_text(source)
walker.inspect(&file, voidptr(0), fn (node ast.Node, data voidptr) bool {
// Second visit must be ast.Stmt
if node is ast.Stmt {
if node !is ast.Module {
// Proceed to another node
return false
}
assert node is ast.Module
mod := node as ast.Module
assert mod.name == 'main'
return false
}
// First visit must be ast.File
assert node is ast.File
// True means that the inspector must now
// inspect the ast.File's children
return true
})
}

View File

@ -90,7 +90,7 @@ pub fn (mut c Checker) check(ast_file &ast.File) {
for i, ast_import in ast_file.imports {
for j in 0 .. i {
if ast_import.mod == ast_file.imports[j].mod {
c.error('module name `$ast_import.mod` duplicate', ast_import.pos)
c.error('module name `$ast_import.mod` duplicate', ast_import.mod_pos)
}
}
}

View File

@ -60,7 +60,7 @@ fn (mut p Parser) check_unused_imports() {
mod := import_m.mod
if !p.is_used_import(alias) {
mod_alias := if alias == mod { alias } else { '$alias ($mod)' }
p.warn_with_pos("module '$mod_alias' is imported but never used", import_m.pos)
p.warn_with_pos("module '$mod_alias' is imported but never used", import_m.mod_pos)
}
}
}

View File

@ -1683,27 +1683,35 @@ fn (mut p Parser) module_decl() ast.Module {
mut name := 'main'
is_skipped := p.tok.kind != .key_module
mut module_pos := token.Position{}
mut name_pos := token.Position{}
mut mod_node := ast.Module{}
if !is_skipped {
p.attrs = []
module_pos = p.tok.position()
p.next()
mut pos := p.tok.position()
name_pos = p.tok.position()
name = p.check_name()
if module_pos.line_nr != pos.line_nr {
p.error_with_pos('`module` and `$name` must be at same line', pos)
return ast.Module{}
mod_node = ast.Module{
pos: module_pos
}
pos = p.tok.position()
if module_pos.line_nr == pos.line_nr && p.tok.kind != .comment {
if module_pos.line_nr != name_pos.line_nr {
p.error_with_pos('`module` and `$name` must be at same line', name_pos)
return mod_node
}
// NB: this shouldn't be reassigned into name_pos
// as it creates a wrong position when extended
// to module_pos
n_pos := p.tok.position()
if module_pos.line_nr == n_pos.line_nr && p.tok.kind != .comment {
if p.tok.kind != .name {
p.error_with_pos('`module x` syntax error', pos)
return ast.Module{}
p.error_with_pos('`module x` syntax error', n_pos)
return mod_node
} else {
p.error_with_pos('`module x` can only declare one module', pos)
return ast.Module{}
p.error_with_pos('`module x` can only declare one module', n_pos)
return mod_node
}
}
module_pos = module_pos.extend(pos)
module_pos = module_pos.extend(name_pos)
}
mut full_mod := p.table.qualify_module(name, p.file_name)
if p.pref.build_mode == .build_module && !full_mod.contains('.') {
@ -1721,6 +1729,13 @@ fn (mut p Parser) module_decl() ast.Module {
}
p.mod = full_mod
p.builtin_mod = p.mod == 'builtin'
mod_node = ast.Module{
name: full_mod
attrs: module_attrs
is_skipped: is_skipped
pos: module_pos
name_pos: name_pos
}
if !is_skipped {
for ma in module_attrs {
match ma.name {
@ -1729,78 +1744,103 @@ fn (mut p Parser) module_decl() ast.Module {
}
else {
p.error_with_pos('unknown module attribute `[$ma.name]`', ma.pos)
return ast.Module{}
return mod_node
}
}
}
}
return ast.Module{
name: full_mod
attrs: module_attrs
is_skipped: is_skipped
pos: module_pos
}
return mod_node
}
fn (mut p Parser) import_stmt() ast.Import {
import_pos := p.tok.position()
p.check(.key_import)
pos := p.tok.position()
mut pos := p.tok.position()
mut import_node := ast.Import{
pos: import_pos.extend(pos)
}
if p.tok.kind == .lpar {
p.error_with_pos('`import()` has been deprecated, use `import x` instead', pos)
return ast.Import{}
return import_node
}
mut mod_name := p.check_name()
mut mod_name_arr := []string{}
mod_name_arr << p.check_name()
if import_pos.line_nr != pos.line_nr {
p.error_with_pos('`import` statements must be a single line', pos)
return ast.Import{}
return import_node
}
mut mod_alias := mod_name_arr[0]
import_node = ast.Import{
pos: import_pos.extend(pos)
mod_pos: pos
alias_pos: pos
}
mut mod_alias := mod_name
for p.tok.kind == .dot {
p.next()
pos_t := p.tok.position()
submod_pos := p.tok.position()
if p.tok.kind != .name {
p.error_with_pos('module syntax error, please use `x.y.z`', pos)
return ast.Import{}
p.error_with_pos('module syntax error, please use `x.y.z`', submod_pos)
return import_node
}
if import_pos.line_nr != pos_t.line_nr {
p.error_with_pos('`import` and `submodule` must be at same line', pos)
return ast.Import{}
if import_pos.line_nr != submod_pos.line_nr {
p.error_with_pos('`import` and `submodule` must be at same line', submod_pos)
return import_node
}
submod_name := p.check_name()
mod_name += '.' + submod_name
mod_name_arr << submod_name
mod_alias = submod_name
}
if p.tok.kind == .key_as {
p.next()
mod_alias = p.check_name()
if mod_alias == mod_name.split('.').last() {
p.error_with_pos('import alias `$mod_name as $mod_alias` is redundant', p.prev_tok.position())
return ast.Import{}
pos = pos.extend(submod_pos)
import_node = ast.Import{
pos: import_pos.extend(pos)
mod_pos: pos
alias_pos: submod_pos
mod: mod_name_arr.join('.')
alias: mod_alias
}
}
mut node := ast.Import{
pos: pos
mod: mod_name
alias: mod_alias
if mod_name_arr.len == 1 {
import_node = ast.Import{
pos: import_node.pos
mod_pos: import_node.mod_pos
alias_pos: import_node.alias_pos
mod: mod_name_arr[0]
alias: mod_alias
}
}
mod_name := import_node.mod
if p.tok.kind == .key_as {
p.next()
alias_pos := p.tok.position()
mod_alias = p.check_name()
if mod_alias == mod_name_arr.last() {
p.error_with_pos('import alias `$mod_name as $mod_alias` is redundant', p.prev_tok.position())
return import_node
}
import_node = ast.Import{
pos: import_node.pos.extend(alias_pos)
mod_pos: import_node.mod_pos
alias_pos: alias_pos
mod: import_node.mod
alias: mod_alias
}
}
if p.tok.kind == .lcbr { // import module { fn1, Type2 } syntax
p.import_syms(mut node)
p.import_syms(mut import_node)
p.register_used_import(mod_alias) // no `unused import` msg for parent
}
pos_t := p.tok.position()
if import_pos.line_nr == pos_t.line_nr {
if p.tok.kind !in [.lcbr, .eof, .comment] {
p.error_with_pos('cannot import multiple modules at a time', pos_t)
return ast.Import{}
return import_node
}
}
p.imports[mod_alias] = mod_name
// if mod_name !in p.table.imports {
p.table.imports << mod_name
p.ast_imports << node
p.ast_imports << import_node
// }
return node
return import_node
}
// import_syms parses the inner part of `import module { submod1, submod2 }`