Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a new file tag parser #492

Merged
merged 1 commit into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 84 additions & 108 deletions src/common/ast.odin
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "core:fmt"
import "core:log"
import "core:mem"
import "core:odin/ast"
import "core:odin/parser"
import path "core:path/slashpath"
import "core:strings"

Expand Down Expand Up @@ -69,16 +70,15 @@ keyword_map: map[string]bool = {
}

GlobalExpr :: struct {
name: string,
name_expr: ^ast.Expr,
expr: ^ast.Expr,
mutable: bool,
docs: ^ast.Comment_Group,
attributes: []^ast.Attribute,
deprecated: bool,
file_private: bool,
package_private: bool,
builtin: bool,
name: string,
name_expr: ^ast.Expr,
expr: ^ast.Expr,
mutable: bool,
docs: ^ast.Comment_Group,
attributes: []^ast.Attribute,
deprecated: bool,
private: parser.Private_Flag,
builtin: bool,
}

get_attribute_objc_type :: proc(attributes: []^ast.Attribute) -> ^ast.Expr {
Expand Down Expand Up @@ -240,116 +240,92 @@ is_expr_basic_lit :: proc(expr: ^ast.Expr) -> bool {
return ok
}

collect_value_decl :: proc(exprs: ^[dynamic]GlobalExpr, file: ast.File, stmt: ^ast.Node, skip_private: bool) {
if value_decl, ok := stmt.derived.(^ast.Value_Decl); ok {
is_deprecated := false
is_private_file := false
is_private_pkg := false
is_builtin := false

for attribute in value_decl.attributes {
for elem in attribute.elems {
if value, ok := elem.derived.(^ast.Field_Value); ok {
if ident, ok := value.field.derived.(^ast.Ident); ok {
switch ident.name {
case "private":
if val, ok := value.value.derived.(^ast.Basic_Lit); ok {
switch val.tok.text {
case "\"file\"":
is_private_file = true
case "package":
is_private_pkg = true
}
} else {
is_private_pkg = true
}
}
}
} else if ident, ok := elem.derived.(^ast.Ident); ok {
switch ident.name {
case "deprecated":
is_deprecated = true
case "builtin":
is_builtin = true
case "private":
is_private_pkg = true
}
}
}
}
collect_value_decl :: proc(
exprs: ^[dynamic]GlobalExpr,
file: ast.File,
file_tags: parser.File_Tags,
stmt: ^ast.Node,
skip_private: bool,
) {
value_decl, is_value_decl := stmt.derived.(^ast.Value_Decl)

if is_private_file && skip_private {
return
}
if !is_value_decl {
return
}

// If a private status is not explicitly set with an attribute above the declaration
// check the file comment.
if !is_private_file && !is_private_pkg && file.docs != nil {
for comment in file.docs.list {
txt := comment.text
if strings.has_prefix(txt, "//+private") {
txt = strings.trim_prefix(txt, "//+private")
is_private_pkg = true

if strings.has_prefix(txt, " ") {
txt = strings.trim_space(txt)
if txt == "file" {
is_private_file = true
}
global_expr := GlobalExpr{
mutable = value_decl.is_mutable,
docs = value_decl.docs,
attributes = value_decl.attributes[:],
private = file_tags.private,
}

for attribute in value_decl.attributes {
for elem in attribute.elems {
ident: ^ast.Ident
value: ast.Any_Node

#partial switch v in elem.derived {
case ^ast.Field_Value:
ident = v.field.derived.(^ast.Ident) or_continue
value = v.value.derived
case ^ast.Ident:
ident = v
case:
continue
}

switch ident.name {
case "deprecated":
global_expr.deprecated = true
case "builtin":
global_expr.builtin = true
case "private":
if val, ok := value.(^ast.Basic_Lit); ok {
switch val.tok.text {
case "\"file\"":
global_expr.private = .File
case "\"package\"":
global_expr.private = .Package
}
} else if strings.has_prefix(txt, "//+build ignore") {
is_private_pkg = true
is_private_file = true
} else {
global_expr.private = .Package
}
}
}
}

for name, i in value_decl.names {
str := get_ast_node_string(name, file.src)

if value_decl.type != nil {
append(
exprs,
GlobalExpr {
name = str,
name_expr = name,
expr = value_decl.type,
mutable = value_decl.is_mutable,
docs = value_decl.docs,
attributes = value_decl.attributes[:],
deprecated = is_deprecated,
builtin = is_builtin,
package_private = is_private_pkg,
},
)
} else {
if len(value_decl.values) > i {
append(
exprs,
GlobalExpr {
name = str,
name_expr = name,
expr = value_decl.values[i],
mutable = value_decl.is_mutable,
docs = value_decl.docs,
attributes = value_decl.attributes[:],
deprecated = is_deprecated,
builtin = is_builtin,
package_private = is_private_pkg,
},
)
}
}
if file_tags.ignore {
global_expr.private = .File
}

if skip_private && global_expr.private == .File {
return
}

for name, i in value_decl.names {
global_expr.name = get_ast_node_string(name, file.src)
global_expr.name_expr = name

if value_decl.type != nil {
global_expr.expr = value_decl.type
append(exprs, global_expr)
} else if len(value_decl.values) > i {
global_expr.expr = value_decl.values[i]
append(exprs, global_expr)
}
}
}

collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr {
exprs := make([dynamic]GlobalExpr, context.temp_allocator)
defer shrink(&exprs)

file_tags := parser.parse_file_tags(file, context.temp_allocator)

for decl in file.decls {
if value_decl, ok := decl.derived.(^ast.Value_Decl); ok {
collect_value_decl(&exprs, file, decl, skip_private)
collect_value_decl(&exprs, file, file_tags, decl, skip_private)
} else if when_decl, ok := decl.derived.(^ast.When_Stmt); ok {
if when_decl.cond == nil {
continue
Expand Down Expand Up @@ -395,21 +371,21 @@ collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr {
if allowed {
if block, ok := when_decl.body.derived.(^ast.Block_Stmt); ok {
for stmt in block.stmts {
collect_value_decl(&exprs, file, stmt, skip_private)
collect_value_decl(&exprs, file, file_tags, stmt, skip_private)
}
}
} else if ident.name != "ODIN_OS" && ident.name != "ODIN_ARCH" {
if block, ok := when_decl.body.derived.(^ast.Block_Stmt); ok {
for stmt in block.stmts {
collect_value_decl(&exprs, file, stmt, skip_private)
collect_value_decl(&exprs, file, file_tags, stmt, skip_private)
}
}
}
}
} else {
if block, ok := when_decl.body.derived.(^ast.Block_Stmt); ok {
for stmt in block.stmts {
collect_value_decl(&exprs, file, stmt, skip_private)
collect_value_decl(&exprs, file, file_tags, stmt, skip_private)
}
}
}
Expand All @@ -420,7 +396,7 @@ collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr {

if block, ok := foreign_decl.body.derived.(^ast.Block_Stmt); ok {
for stmt in block.stmts {
collect_value_decl(&exprs, file, stmt, skip_private)
collect_value_decl(&exprs, file, file_tags, stmt, skip_private)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/server/collector.odin
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,11 @@ collect_symbols :: proc(collection: ^SymbolCollection, file: ast.File, uri: stri
symbol.flags |= {.Deprecated}
}

if expr.file_private {
if expr.private == .File {
symbol.flags |= {.PrivateFile}
}

if expr.package_private {
if expr.private == .Package {
symbol.flags |= {.PrivatePackage}
}

Expand Down
39 changes: 39 additions & 0 deletions tests/completions_test.odin
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tests

import "core:fmt"
import "core:testing"
import "core:strings"

import test "src:testing"

Expand Down Expand Up @@ -1069,6 +1070,44 @@ ast_file_private_completion :: proc(t: ^testing.T) {
test.expect_completion_details(t, &source, ".", {})
}

@(test)
ast_file_tag_private_completion :: proc(t: ^testing.T) {
comments := []string{
"// +private",
"//+private file",
"// +build ignore",
}

for comment in comments {

b := strings.builder_make(context.temp_allocator)

strings.write_string(&b, comment)
strings.write_string(&b, `
package my_package

my_proc :: proc() -> bool {}
`)

source := test.Source {
main = `package main
import "my_package"
main :: proc() {
my_package.{*}
}
`,
packages = {
{
pkg = "my_package",
source = strings.to_string(b),
}
},
}

test.expect_completion_details(t, &source, ".", {})
}
}

@(test)
ast_non_mutable_variable_struct_completion :: proc(t: ^testing.T) {
packages := make([dynamic]test.Package, context.temp_allocator)
Expand Down