v.ast: add walker submodule for ast walking (#7775)
parent
eff757d0a1
commit
256ddcee1f
190
vlib/v/ast/ast.v
190
vlib/v/ast/ast.v
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }`
|
||||
|
|
Loading…
Reference in New Issue