From d41725f89f3db242443a4c8c30883016abfaabce Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 7 Oct 2023 21:37:36 -0700 Subject: [PATCH] Do not include files that have already been included. If two #included files have the same inode, only the first include will be parsed. This obsoletes the "#if ... #endinput" hack in every include file. Nominally #include in SourcePawn was the same as C/C++, where you can include the same file over and over. This is useful in C/C++ for creating macro expansion tables. It's not useful in Pawn, where those tricks don't work, and as evidenced by the fact that not a single plugin in the corpus was affected by this change. This greatly simplifies how static scopes are handled, since SourcePawn's notion of a static scope is per-file, rather than per-translation unit. This simplification re-opens the door to multiple translation units, an important step for an upcoming refactoring of how builtins work. --- compiler/compile-context.h | 12 ++++----- compiler/lexer.cpp | 4 +++ compiler/parser.cpp | 51 +++++++++++--------------------------- compiler/parser.h | 10 +++++--- compiler/source-file.h | 4 +++ compiler/stl/stl-deque.h | 31 +++++++++++++++++++++++ 6 files changed, 67 insertions(+), 45 deletions(-) create mode 100644 compiler/stl/stl-deque.h diff --git a/compiler/compile-context.h b/compiler/compile-context.h index 3647577c7..e1e6497bf 100644 --- a/compiler/compile-context.h +++ b/compiler/compile-context.h @@ -62,7 +62,7 @@ class CompileContext final void TrackMalloc(size_t bytes); void TrackFree(size_t bytes); - sp::SymbolScope* globals() const { return globals_; } + SymbolScope* globals() const { return globals_; } tr::unordered_set& functions() { return functions_; } tr::unordered_set& publics() { return publics_; } const std::shared_ptr& lexer() const { return lexer_; } @@ -104,8 +104,8 @@ class CompileContext final std::string& outfname() { return outfname_; } void set_outfname(const std::string& value) { outfname_ = value; } - std::shared_ptr inpf_org() const { return inpf_org_; } - void set_inpf_org(std::shared_ptr sf) { inpf_org_ = sf; } + std::shared_ptr inpf_org() const { return inpf_org_; } + void set_inpf_org(std::shared_ptr sf) { inpf_org_ = sf; } bool must_abort() const { return must_abort_; } void set_must_abort() { must_abort_ = true; } @@ -130,7 +130,7 @@ class CompileContext final private: cc::PoolAllocator allocator_; - sp::SymbolScope* globals_; + SymbolScope* globals_; std::string default_include_; tr::unordered_set functions_; tr::unordered_set publics_; @@ -138,9 +138,9 @@ class CompileContext final std::string outfname_; std::string errfname_; std::unique_ptr sources_; - std::shared_ptr inpf_org_; + std::shared_ptr inpf_org_; std::unique_ptr types_; - sp::StringPool atoms_; + StringPool atoms_; // The lexer is in CompileContext rather than Parser until we can eliminate // PreprocExpr(). diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index 1303cb1c2..08f1e8df8 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -77,6 +77,8 @@ bool Lexer::PlungeQualifiedFile(const std::string& name) { auto fp = OpenFile(name); if (!fp) return false; + if (fp->included()) + return true; assert(!IsSkipping()); assert(skiplevel_ == ifstack_.size()); /* these two are always the same when "parsing" */ @@ -2313,6 +2315,8 @@ void Lexer::EnterFile(std::shared_ptr&& sf, const token_pos_t& from) SkipUtf8Bom(); SetFileDefines(state_.inpf->name()); + state_.inpf->set_included(); + tokens_on_line_ = 0; } diff --git a/compiler/parser.cpp b/compiler/parser.cpp index 157a65155..5e6284ff7 100644 --- a/compiler/parser.cpp +++ b/compiler/parser.cpp @@ -59,7 +59,11 @@ Parser::Parse() }); std::vector stmts; - CreateInitialScopes(&stmts); + + if (!cc_.default_include().empty()) { + auto incfname = cc_.default_include(); + lexer_->PlungeFile(incfname, FALSE, TRUE); + } // Prime the lexer. lexer_->Start(); @@ -70,20 +74,9 @@ Parser::Parse() int tok = lexer_->lex(); - // We don't have end-of-file tokens (yet), so we pop static scopes - // before every declaration. This should be after lexer_->lex() so we've - // processed any end-of-file events. - bool changed = false; - int fcurrent = lexer_->fcurrent(); - while (!static_scopes_.empty() && static_scopes_.back()->fnumber() != fcurrent) { - changed = true; - static_scopes_.pop_back(); - } - assert(!static_scopes_.empty()); - - if (changed) { - stmts.emplace_back(new ChangeScopeNode(lexer_->pos(), static_scopes_.back(), - lexer_->inpf()->name())); + if (sources_index_ != lexer_->fcurrent()) { + ChangeStaticScope(&stmts); + sources_index_ = lexer_->fcurrent(); } switch (tok) { @@ -156,10 +149,6 @@ Parser::Parse() report(417) << name.substr(1); cc_.set_must_abort(); } - - int fcurrent = lexer_->fcurrent(); - static_scopes_.emplace_back(new SymbolScope(cc_.globals(), sFILE_STATIC, fcurrent)); - decl = new ChangeScopeNode(lexer_->pos(), static_scopes_.back(), name.substr(1)); break; } case '}': @@ -212,25 +201,15 @@ Parser::Parse() return new ParseTree(list); } -void Parser::CreateInitialScopes(std::vector* stmts) { - // Create a static scope for the main file. - { - int fcurrent = lexer_->fcurrent(); - assert(fcurrent == 0); - static_scopes_.emplace_back(new SymbolScope(cc_.globals(), sFILE_STATIC, fcurrent)); - stmts->emplace_back(new ChangeScopeNode({}, static_scopes_.back(), - lexer_->inpf()->name())); +void Parser::ChangeStaticScope(std::vector* stmts) { + auto sources_index = lexer_->fcurrent(); + auto iter = static_scopes_.find(sources_index); + if (iter == static_scopes_.end()) { + auto scope = new SymbolScope(cc_.globals(), sFILE_STATIC, sources_index); + iter = static_scopes_.emplace(sources_index, scope).first; } - if (!cc_.default_include().empty()) { - auto incfname = cc_.default_include(); - if (lexer_->PlungeFile(incfname, FALSE, TRUE)) { - int fcurrent = lexer_->fcurrent(); - static_scopes_.emplace_back(new SymbolScope(cc_.globals(), sFILE_STATIC, fcurrent)); - stmts->emplace_back(new ChangeScopeNode({}, static_scopes_.back(), - lexer_->inpf()->name())); - } - } + stmts->emplace_back(new ChangeScopeNode(lexer_->pos(), iter->second, lexer_->inpf()->name())); } Stmt* diff --git a/compiler/parser.h b/compiler/parser.h index 21ad8790b..f3bf2891e 100644 --- a/compiler/parser.h +++ b/compiler/parser.h @@ -25,6 +25,7 @@ #include "parse-node.h" #include "sc.h" #include "sctracker.h" +#include "stl/stl-deque.h" namespace sp { @@ -44,8 +45,10 @@ class Parser typedef int (Parser::*HierFn)(value*); typedef Expr* (Parser::*NewHierFn)(); + static symbol* ParseInlineFunction(int tokid, const declinfo_t& decl, const int* this_tag); - void CreateInitialScopes(std::vector* list); + + void ChangeStaticScope(std::vector* stmts); Stmt* parse_unknown_decl(const full_token_t* tok); Decl* parse_enum(int vclass); @@ -135,10 +138,11 @@ class Parser Semantics* sema_; bool in_loop_ = false; bool in_test_ = false; - std::vector static_scopes_; std::shared_ptr lexer_; TypeDictionary* types_ = nullptr; - std::deque delayed_functions_; + tr::deque delayed_functions_; + tr::unordered_map static_scopes_; + int sources_index_ = -1; }; } // namespace sp diff --git a/compiler/source-file.h b/compiler/source-file.h index f08a0987f..6e10ed90c 100644 --- a/compiler/source-file.h +++ b/compiler/source-file.h @@ -52,6 +52,9 @@ class SourceFile : public std::enable_shared_from_this bool is_main_file() const { return is_main_file_; } void set_is_main_file() { is_main_file_ = true; } + bool included() const { return included_; } + void set_included() { included_ = true; } + void operator =(const SourceFile&) = delete; void operator =(SourceFile&&) = delete; const unsigned char* data() const { @@ -75,6 +78,7 @@ class SourceFile : public std::enable_shared_from_this tr::string data_; size_t pos_; bool is_main_file_ = false; + bool included_ = false; ke::Maybe sources_index_; tr::vector line_extents_; }; diff --git a/compiler/stl/stl-deque.h b/compiler/stl/stl-deque.h new file mode 100644 index 000000000..01e1602d5 --- /dev/null +++ b/compiler/stl/stl-deque.h @@ -0,0 +1,31 @@ +// vim: set sts=4 ts=8 sw=4 tw=99 et: +// +// Copyright (C) 2023 AlliedModders LLC +// +// SourcePawn is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// SourcePawn is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with +// SourcePawn. If not, see http://www.gnu.org/licenses/. + +#pragma once + +#include + +#include "stl-allocator.h" + +namespace sp { +namespace tr { + +template +using deque = std::deque>; + +} // namespace tr +} // namespace sp +