Merge pull request #4203 from karl-zylinski/file-tags-without-comments

Make tags use #+ syntax instead of //+
This commit is contained in:
gingerBill
2024-09-19 12:36:43 +01:00
committed by GitHub
332 changed files with 665 additions and 510 deletions
+129 -54
View File
@@ -4967,7 +4967,7 @@ gb_internal Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
}
if (f->in_when_statement) {
syntax_error(import_name, "Cannot use 'import' within a 'when' statement. Prefer using the file suffixes (e.g. foo_windows.odin) or '//+build' tags");
syntax_error(import_name, "Cannot use 'import' within a 'when' statement. Prefer using the file suffixes (e.g. foo_windows.odin) or '#+build' tags");
}
if (kind != ImportDecl_Standard) {
@@ -5355,6 +5355,12 @@ gb_internal Ast *parse_stmt(AstFile *f) {
s = ast_empty_stmt(f, token);
expect_semicolon(f);
return s;
case Token_FileTag:
// This is always an error because all valid file tags will have been processed in `parse_file` already.
// Any remaining file tags must be past the package line and thus invalid.
syntax_error(token, "Lines starting with #+ (file tags) are only allowed before the package line.");
return ast_bad_stmt(f, token, f->curr_token);
}
// Error correction statements
@@ -6086,7 +6092,7 @@ gb_internal String build_tag_get_token(String s, String *out) {
}
gb_internal bool parse_build_tag(Token token_for_pos, String s) {
String const prefix = str_lit("+build");
String const prefix = str_lit("build");
GB_ASSERT(string_starts_with(s, prefix));
s = string_trim_whitespace(substring(s, prefix.len, s.len));
@@ -6099,6 +6105,10 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
while (s.len > 0) {
bool this_kind_correct = true;
bool this_kind_os_seen = false;
bool this_kind_arch_seen = false;
int num_tokens = 0;
do {
String p = string_trim_whitespace(build_tag_get_token(s, &s));
if (p.len == 0) break;
@@ -6124,7 +6134,18 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
TargetOsKind os = get_target_os_from_string(p);
TargetArchKind arch = get_target_arch_from_string(p);
num_tokens += 1;
// Catches 'windows linux', which is an impossible combination.
// Also catches usage of more than two things within a comma separated group.
if (num_tokens > 2 || (this_kind_os_seen && os != TargetOs_Invalid) || (this_kind_arch_seen && arch != TargetArch_Invalid)) {
syntax_error(token_for_pos, "Invalid build tag: Missing ',' before '%.*s'. Format: '#+build linux, windows amd64, darwin'", LIT(p));
break;
}
if (os != TargetOs_Invalid) {
this_kind_os_seen = true;
GB_ASSERT(arch == TargetArch_Invalid);
if (is_notted) {
this_kind_correct = this_kind_correct && (os != build_context.metrics.os);
@@ -6132,6 +6153,8 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
this_kind_correct = this_kind_correct && (os == build_context.metrics.os);
}
} else if (arch != TargetArch_Invalid) {
this_kind_arch_seen = true;
if (is_notted) {
this_kind_correct = this_kind_correct && (arch != build_context.metrics.arch);
} else {
@@ -6171,7 +6194,7 @@ gb_internal String vet_tag_get_token(String s, String *out) {
gb_internal u64 parse_vet_tag(Token token_for_pos, String s) {
String const prefix = str_lit("+vet");
String const prefix = str_lit("vet");
GB_ASSERT(string_starts_with(s, prefix));
s = string_trim_whitespace(substring(s, prefix.len, s.len));
@@ -6276,7 +6299,7 @@ gb_internal isize calc_decl_count(Ast *decl) {
}
gb_internal bool parse_build_project_directory_tag(Token token_for_pos, String s) {
String const prefix = str_lit("+build-project-name");
String const prefix = str_lit("build-project-name");
GB_ASSERT(string_starts_with(s, prefix));
s = string_trim_whitespace(substring(s, prefix.len, s.len));
if (s.len == 0) {
@@ -6320,6 +6343,48 @@ gb_internal bool parse_build_project_directory_tag(Token token_for_pos, String s
return any_correct;
}
gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) {
if (string_starts_with(lc, str_lit("build-project-name"))) {
if (!parse_build_project_directory_tag(tok, lc)) {
return false;
}
} else if (string_starts_with(lc, str_lit("build"))) {
if (!parse_build_tag(tok, lc)) {
return false;
}
} else if (string_starts_with(lc, str_lit("vet"))) {
f->vet_flags = parse_vet_tag(tok, lc);
f->vet_flags_set = true;
} else if (string_starts_with(lc, str_lit("ignore"))) {
return false;
} else if (string_starts_with(lc, str_lit("private"))) {
f->flags |= AstFile_IsPrivatePkg;
String command = string_trim_starts_with(lc, str_lit("private "));
command = string_trim_whitespace(command);
if (lc == "private") {
f->flags |= AstFile_IsPrivatePkg;
} else if (command == "package") {
f->flags |= AstFile_IsPrivatePkg;
} else if (command == "file") {
f->flags |= AstFile_IsPrivateFile;
}
} else if (lc == "lazy") {
if (build_context.ignore_lazy) {
// Ignore
} else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) {
// Ignore
} else {
f->flags |= AstFile_IsLazy;
}
} else if (lc == "no-instrumentation") {
f->flags |= AstFile_NoInstrumentation;
} else {
error(tok, "Unknown tag '%.*s'", LIT(lc));
}
return true;
}
gb_internal bool parse_file(Parser *p, AstFile *f) {
if (f->tokens.count == 0) {
return true;
@@ -6338,9 +6403,34 @@ gb_internal bool parse_file(Parser *p, AstFile *f) {
CommentGroup *docs = f->lead_comment;
Array<Token> tags = array_make<Token>(temporary_allocator());
bool first_invalid_token_set = false;
Token first_invalid_token = {};
while (f->curr_token.kind != Token_package && f->curr_token.kind != Token_EOF) {
if (f->curr_token.kind == Token_Comment) {
consume_comment_groups(f, f->prev_token);
} else if (f->curr_token.kind == Token_FileTag) {
array_add(&tags, f->curr_token);
advance_token(f);
} else {
if (!first_invalid_token_set) {
first_invalid_token_set = true;
first_invalid_token = f->curr_token;
}
advance_token(f);
}
}
if (f->curr_token.kind != Token_package) {
ERROR_BLOCK();
syntax_error(f->curr_token, "Expected a package declaration at the beginning of the file");
// The while loop above scanned until it found the package token. If we never
// found one, then make this error appear on the first invalid token line.
Token t = first_invalid_token_set ? first_invalid_token : f->curr_token;
syntax_error(t, "Expected a package declaration at the beginning of the file");
// IMPORTANT NOTE(bill): this is technically a race condition with the suggestion, but it's ony a suggession
// so in practice is should be "fine"
if (f->pkg && f->pkg->name != "") {
@@ -6349,18 +6439,16 @@ gb_internal bool parse_file(Parser *p, AstFile *f) {
return false;
}
// There was an OK package declaration. But there some invalid token was hit before the package declaration.
if (first_invalid_token_set) {
syntax_error(first_invalid_token, "Expected only comments or lines starting with '#+' before the package declaration");
return false;
}
f->package_token = expect_token(f, Token_package);
if (f->package_token.kind != Token_package) {
return false;
}
if (docs != nullptr) {
TokenPos end = token_pos_end(docs->list[docs->list.count-1]);
if (end.line == f->package_token.pos.line || end.line+1 == f->package_token.pos.line) {
// Okay
} else {
docs = nullptr;
}
}
Token package_name = expect_token_after(f, Token_Ident, "package");
if (package_name.kind == Token_Ident) {
@@ -6374,53 +6462,40 @@ gb_internal bool parse_file(Parser *p, AstFile *f) {
}
f->package_name = package_name.string;
if (!f->pkg->is_single_file && docs != nullptr && docs->list.count > 0) {
for (Token const &tok : docs->list) {
GB_ASSERT(tok.kind == Token_Comment);
String str = tok.string;
if (string_starts_with(str, str_lit("//"))) {
// TODO: Shouldn't single file only matter for build tags? no-instrumentation for example
// should be respected even when in single file mode.
if (!f->pkg->is_single_file) {
if (docs != nullptr && docs->list.count > 0) {
for (Token const &tok : docs->list) {
GB_ASSERT(tok.kind == Token_Comment);
String str = tok.string;
if (!string_starts_with(str, str_lit("//"))) {
continue;
}
String lc = string_trim_whitespace(substring(str, 2, str.len));
if (lc.len > 0 && lc[0] == '+') {
if (string_starts_with(lc, str_lit("+build-project-name"))) {
if (!parse_build_project_directory_tag(tok, lc)) {
return false;
}
} else if (string_starts_with(lc, str_lit("+build"))) {
if (!parse_build_tag(tok, lc)) {
return false;
}
} else if (string_starts_with(lc, str_lit("+vet"))) {
f->vet_flags = parse_vet_tag(tok, lc);
f->vet_flags_set = true;
} else if (string_starts_with(lc, str_lit("+ignore"))) {
if (string_starts_with(lc, str_lit("+"))) {
syntax_warning(tok, "'//+' is deprecated: Use '#+' instead");
String lt = substring(lc, 1, lc.len);
if (parse_file_tag(lt, tok, f) == false) {
return false;
} else if (string_starts_with(lc, str_lit("+private"))) {
f->flags |= AstFile_IsPrivatePkg;
String command = string_trim_starts_with(lc, str_lit("+private "));
command = string_trim_whitespace(command);
if (lc == "+private") {
f->flags |= AstFile_IsPrivatePkg;
} else if (command == "package") {
f->flags |= AstFile_IsPrivatePkg;
} else if (command == "file") {
f->flags |= AstFile_IsPrivateFile;
}
} else if (lc == "+lazy") {
if (build_context.ignore_lazy) {
// Ignore
} else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) {
// Ignore
} else {
f->flags |= AstFile_IsLazy;
}
} else if (lc == "+no-instrumentation") {
f->flags |= AstFile_NoInstrumentation;
} else {
warning(tok, "Ignoring unknown tag '%.*s'", LIT(lc));
}
}
}
}
for (Token const &tok : tags) {
GB_ASSERT(tok.kind == Token_FileTag);
String str = tok.string;
if (string_starts_with(str, str_lit("#+"))) {
String lt = string_trim_whitespace(substring(str, 2, str.len));
if (parse_file_tag(lt, tok, f) == false) {
return false;
}
}
}
}
Ast *pd = ast_package_decl(f, f->package_token, package_name, docs, f->line_comment);
+15
View File
@@ -2,6 +2,7 @@
TOKEN_KIND(Token_Invalid, "Invalid"), \
TOKEN_KIND(Token_EOF, "EOF"), \
TOKEN_KIND(Token_Comment, "Comment"), \
TOKEN_KIND(Token_FileTag, "FileTag"), \
\
TOKEN_KIND(Token__LiteralBegin, ""), \
TOKEN_KIND(Token_Ident, "identifier"), \
@@ -939,6 +940,20 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
if (t->curr_rune == '!') {
token->kind = Token_Comment;
tokenizer_skip_line(t);
} else if (t->curr_rune == '+') {
token->kind = Token_FileTag;
// Skip until end of line or until we hit what is probably a comment.
// The parsing of tags happens in `parse_file`.
while (t->curr_rune != GB_RUNE_EOF) {
if (t->curr_rune == '\n') {
break;
}
if (t->curr_rune == '/') {
break;
}
advance_to_next_rune(t);
}
}
break;
case '/':