diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 9088da0ea..d67eb31f3 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -69,6 +69,7 @@ File :: struct { fullpath: string, src: string, + tags: [dynamic]tokenizer.Token, docs: ^Comment_Group, pkg_decl: ^Package_Decl, diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin index b12c3b5fd..e2a07f8fb 100644 --- a/core/odin/parser/file_tags.odin +++ b/core/odin/parser/file_tags.odin @@ -51,7 +51,7 @@ get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags: File_Tags) { context.allocator = allocator - if file.docs == nil { + if file.docs == nil && file.tags == nil { return } @@ -95,11 +95,9 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags build_project_names: [dynamic][]string defer shrink(&build_project_names) - for comment in file.docs.list { - if len(comment.text) < 3 || comment.text[:2] != "//" { - continue - } - text := comment.text[2:] + parse_tag :: proc(text: string, tags: ^File_Tags, build_kinds: ^[dynamic]Build_Kind, + build_project_name_strings: ^[dynamic]string, + build_project_names: ^[dynamic][]string) { i := 0 skip_whitespace(text, &i) @@ -124,7 +122,7 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags groups_loop: for { index_start := len(build_project_name_strings) - defer append(&build_project_names, build_project_name_strings[index_start:]) + defer append(build_project_names, build_project_name_strings[index_start:]) for { skip_whitespace(text, &i) @@ -143,10 +141,10 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags } scan_value(text, &i) - append(&build_project_name_strings, text[name_start:i]) + append(build_project_name_strings, text[name_start:i]) } - append(&build_project_names, build_project_name_strings[index_start:]) + append(build_project_names, build_project_name_strings[index_start:]) } case "build": kinds_loop: for { @@ -156,7 +154,7 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags arch_positive: runtime.Odin_Arch_Types arch_negative: runtime.Odin_Arch_Types - defer append(&build_kinds, Build_Kind{ + defer append(build_kinds, Build_Kind{ os = (os_positive == {} ? runtime.ALL_ODIN_OS_TYPES : os_positive) -os_negative, arch = (arch_positive == {} ? runtime.ALL_ODIN_ARCH_TYPES : arch_positive)-arch_negative, }) @@ -200,6 +198,27 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags } } + if file.docs != nil { + for comment in file.docs.list { + if len(comment.text) < 3 || comment.text[:2] != "//" { + continue + } + text := comment.text[2:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + } + + for tag in file.tags { + if len(tag.text) < 3 || tag.text[:2] != "#+" { + continue + } + // Only skip # because parse_tag skips the plus + text := tag.text[1:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + tags.build = build_kinds[:] tags.build_project_name = build_project_names[:] diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 5f455c749..d4e532ec7 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -77,9 +77,7 @@ parse_package :: proc(pkg: ^ast.Package, p: ^Parser = nil) -> bool { if !parse_file(p, file) { ok = false } - if file.pkg_decl == nil { - error(p, p.curr_tok.pos, "Expected a package declaration at the start of the file") - } else if pkg.name == "" { + if pkg.name == "" { pkg.name = file.pkg_decl.name } else if pkg.name != file.pkg_decl.name { error(p, file.pkg_decl.pos, "different package name, expected '%s', got '%s'", pkg.name, file.pkg_decl.name) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index aab59c29d..9dfca40b5 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -161,11 +161,36 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { docs := p.lead_comment - p.file.pkg_token = expect_token(p, .Package) - if p.file.pkg_token.kind != .Package { - return false + invalid_pre_package_token: Maybe(tokenizer.Token) + + for p.curr_tok.kind != .Package && p.curr_tok.kind != .EOF { + if p.curr_tok.kind == .Comment { + consume_comment_groups(p, p.prev_tok) + } else if p.curr_tok.kind == .File_Tag { + append(&p.file.tags, p.curr_tok) + advance_token(p) + } else { + if invalid_pre_package_token == nil { + invalid_pre_package_token = p.curr_tok + } + + advance_token(p) + } } + if p.curr_tok.kind != .Package { + t := invalid_pre_package_token.? or_else p.curr_tok + error(p, t.pos, "Expected a package declaration at the start of the file") + return false + } + + p.file.pkg_token = expect_token(p, .Package) + + if ippt, ok := invalid_pre_package_token.?; ok { + error(p, ippt.pos, "Expected only comments or lines starting with '#+' before the package declaration") + return false + } + pkg_name := expect_token_after(p, .Ident, "package") if pkg_name.kind == .Ident { switch name := pkg_name.text; { diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index cd8953841..48d08f127 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -32,6 +32,7 @@ Token_Kind :: enum u32 { Invalid, EOF, Comment, + File_Tag, B_Literal_Begin, Ident, // main @@ -166,6 +167,7 @@ tokens := [Token_Kind.COUNT]string { "Invalid", "EOF", "Comment", + "FileTag", "", "identifier", diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 62170aa10..c3a30581c 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -206,6 +206,23 @@ scan_comment :: proc(t: ^Tokenizer) -> string { return string(lit) } +scan_file_tag :: proc(t: ^Tokenizer) -> string { + offset := t.offset - 1 + + for t.ch != '\n' { + if t.ch == '/' { + next := peek_byte(t, 0) + + if next == '/' || next == '*' { + break + } + } + advance_rune(t) + } + + return string(t.src[offset : t.offset]) +} + scan_identifier :: proc(t: ^Tokenizer) -> string { offset := t.offset @@ -636,6 +653,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '!' { kind = .Comment lit = scan_comment(t) + } else if t.ch == '+' { + kind = .File_Tag + lit = scan_file_tag(t) } case '/': kind = .Quo