diff --git a/compiler/builtins.h b/compiler/builtins.h new file mode 100644 index 000000000..675abf803 --- /dev/null +++ b/compiler/builtins.h @@ -0,0 +1,34 @@ +// vim: set ts=8 sts=4 sw=4 tw=99 et: +// Pawn compiler - File input, preprocessing and lexical analysis functions +// +// Copyright (c) 2023 AlliedModders LLC +// +// This software is provided "as-is", without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from +// the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in +// a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#pragma once + +namespace sp { + +class BuiltinGenerator final { + public: + explicit BuiltinGenerator(CompileContext& cc); + + private: + CompileContext& cc_; +}; + +} // namespace sp diff --git a/compiler/driver.cpp b/compiler/driver.cpp index 1a2b3825e..00e7bae3d 100644 --- a/compiler/driver.cpp +++ b/compiler/driver.cpp @@ -19,6 +19,8 @@ // 3. This notice may not be removed or altered from any source distribution. #include +#include + #include "compile-options.h" #include "errors.h" #include "sc.h" @@ -33,6 +35,8 @@ using namespace ke; using namespace sp; +namespace fs = std::filesystem; + #if defined _WIN32 static HWND hwndFinish = 0; #endif @@ -69,22 +73,6 @@ args::ToggleOption opt_stderr(nullptr, "--use-stderr", Some(false), args::ToggleOption opt_no_verify(nullptr, "--no-verify", Some(false), "Disable opcode verification (for debugging)."); -static std::string get_extension(const std::string& filename) { - auto pos = filename.rfind('.'); - if (pos == std::string::npos) - return {}; - - /* ignore extension on a directory or at the start of the filename */ - if (pos == 0) - return {}; - if (filename[pos - 1] == DIRSEP_CHAR) - return {}; - if (filename.find(DIRSEP_CHAR, pos) != std::string::npos) - return {}; - - return filename.substr(pos); -} - /* set_extension * Set the default extension, or force an extension. To erase the * extension of a filename, set "extension" to an empty string. @@ -93,11 +81,11 @@ static void set_extension(std::string* filename, const char* extension, bool for assert(extension != NULL && (*extension == '\0' || *extension == '.')); assert(filename != NULL); - auto old_ext = get_extension(*filename); - if (force && !old_ext.empty()) - *filename = filename->substr(0, filename->size() - old_ext.size()); - if (force || old_ext.empty()) - *filename += extension; + fs::path path(*filename); + if (force && !path.has_extension()) + *filename = path.stem().string(); + if (force || path.has_extension()) + *filename = fs::path(*filename).replace_extension(extension).string(); } static void Usage(CompileContext& cc, args::Parser& parser, int argc, char** argv) @@ -113,7 +101,7 @@ static void parseoptions(CompileContext& cc, int argc, char** argv) { args::Parser parser; parser.enable_inline_values(); parser.collect_extra_args(); - if (DIRSEP_CHAR != '/') { + if (fs::path::preferred_separator != '/') { parser.allow_slashes(); } @@ -179,8 +167,6 @@ static void parseoptions(CompileContext& cc, int argc, char** argv) { if (str.empty()) continue; - if (str.back() != DIRSEP_CHAR) - str.push_back(DIRSEP_CHAR); cc.options()->include_paths.emplace_back(str); } @@ -197,32 +183,23 @@ static void parseoptions(CompileContext& cc, int argc, char** argv) { } for (const auto& option : parser.extra_args()) { - char str[PATH_MAX]; - const char* ptr = nullptr; - const char* arg = option.c_str(); - if (arg[0] == '@') { + size_t pos; + if (option[0] == '@') { fprintf(stderr, "Response files (@ prefix) are no longer supported."); exit(1); - } else if ((ptr = strchr(arg, '=')) != NULL) { - int i = (int)(ptr - arg); - SafeStrcpyN(str, PATH_MAX, arg, i); - cc.options()->predefines.emplace_back(str, ptr + 1); + } else if ((pos = option.find('=')) != std::string::npos) { + std::string key = option.substr(0, pos); + std::string value = option.substr(pos + 1); + cc.options()->predefines.emplace_back(std::move(key), std::move(value)); } else { - std::string path = arg; - set_extension(&path, ".sp", false); - ke::SafeStrcpy(str, sizeof(str), path.c_str()); + cc.options()->source_files.emplace_back(option); - cc.options()->source_files.emplace_back(str); /* The output name is the first input name with a different extension, * but it is stored in a different directory */ if (cc.outfname().empty()) { - if ((ptr = strrchr(str, DIRSEP_CHAR)) != NULL) - ptr++; /* strip path */ - else - ptr = str; - assert(strlen(ptr) < PATH_MAX); - cc.set_outfname(ptr); + fs::path out_path(option); + cc.set_outfname(out_path.filename().string()); set_extension(&cc.outfname(), ".smx", true); } } diff --git a/compiler/errors.cpp b/compiler/errors.cpp index 2c5eadd64..1334ea522 100644 --- a/compiler/errors.cpp +++ b/compiler/errors.cpp @@ -180,7 +180,7 @@ MessageBuilder::~MessageBuilder() if (report.fileno < cc.sources()->opened_files().size()) report.file = cc.sources()->opened_files().at(report.fileno); - else + else if (!cc.sources()->opened_files().empty()) report.file = cc.sources()->opened_files().at(0); uint32_t actual_line = cc.sources()->GetLineAndCol(where_, &report.col); @@ -194,7 +194,8 @@ MessageBuilder::~MessageBuilder() report.type = DeduceErrorType(number_); std::ostringstream out; - out << report.file->name() << "(" << report.lineno << ") : "; + if (report.file) + out << report.file->name() << "(" << report.lineno << ") : "; out << GetErrorTypePrefix(report.type) << " " << ke::StringPrintf("%03d", report.number) << ": "; diff --git a/compiler/lexer.cpp b/compiler/lexer.cpp index e70a2a00e..1303cb1c2 100644 --- a/compiler/lexer.cpp +++ b/compiler/lexer.cpp @@ -28,8 +28,9 @@ #include #include #include -#include +#include +#include #include #include @@ -62,6 +63,8 @@ namespace sp { +namespace fs = std::filesystem; + // Flags for litchar(). // // Decode utf-8 and error on failure. If unset, non-ASCII characters will be @@ -70,7 +73,7 @@ static constexpr int kLitcharUtf8 = 0x1; // Do not error, because the characters are being ignored. static constexpr int kLitcharSkipping = 0x2; -bool Lexer::PlungeQualifiedFile(const char* name) { +bool Lexer::PlungeQualifiedFile(const std::string& name) { auto fp = OpenFile(name); if (!fp) return false; @@ -104,88 +107,54 @@ std::shared_ptr Lexer::OpenFile(const std::string& name) { } bool -Lexer::PlungeFile(const char* name, int try_currentpath, int try_includepaths) +Lexer::PlungeFile(const std::string& name, int try_currentpath, int try_includepaths) { - bool result = false; - char* pcwd = NULL; - char cwd[PATH_MAX]; - if (try_currentpath) { - result = PlungeQualifiedFile(name); - if (!result) { - /* failed to open the file in the active directory, try to open the file - * in the same directory as the current file --but first check whether - * there is a (relative) path for the current file - */ - const char* ptr; - if ((ptr = strrchr(state_.inpf->name(), DIRSEP_CHAR)) != 0) { - int len = (int)(ptr - state_.inpf->name()) + 1; - if (len + strlen(name) < PATH_MAX) { - char path[PATH_MAX]; - SafeStrcpyN(path, sizeof(path), state_.inpf->name(), len); - SafeStrcat(path, sizeof(path), name); - result = PlungeQualifiedFile(path); - } - } - } else { - pcwd = getcwd(cwd, sizeof(cwd)); - if (!pcwd) { - report(435); - return false; - } + if (PlungeQualifiedFile(name)) + return true; -#ifdef _WIN32 - // make the drive letter on windows lower case to be in line with the rest of SP, as they have a small drive letter in the path - cwd[0] = tolower(cwd[0]); -#endif + // failed to open the file in the active directory, try to open the file + // in the same directory as the current file --but first check whether + // there is a (relative) path for the current file + fs::path current_path(state_.inpf->name()); + auto parent_path = current_path.parent_path(); + if (!parent_path.empty()) { + auto new_path = parent_path / name; + if (PlungeQualifiedFile(new_path.string())) + return true; } } - if (!result && try_includepaths && name[0] != DIRSEP_CHAR) { + if (try_includepaths && !fs::path(name).is_absolute()) { auto& cc = CompileContext::get(); for (const auto& inc_path : cc.options()->include_paths) { - auto path = inc_path + name; - if (PlungeQualifiedFile(path.c_str())) { - result = true; - break; - } + auto path = fs::path(inc_path) / fs::path(name); + if (PlungeQualifiedFile(path.string())) + return true; } } - if (pcwd) { - char path[PATH_MAX]; - SafeSprintf(path, sizeof(path), "%s%s", pcwd, state_.inpf->name()); - SetFileDefines(path); - } else { - SetFileDefines(state_.inpf->name()); - } - - return result; + return false; } -void -Lexer::SetFileDefines(std::string file) -{ - auto sepIndex = file.find_last_of(DIRSEP_CHAR); - - std::string fileName = sepIndex == std::string::npos ? file : file.substr(sepIndex + 1); - - if (DIRSEP_CHAR == '\\') { - auto pos = file.find('\\'); - while (pos != std::string::npos) { - file.insert(pos + 1, 1, '\\'); - pos = file.find('\\', pos + 2); - } +std::string StringizePath(const fs::path& in_path) { + auto path = '"' + in_path.string() + '"'; + auto pos = path.find('\\'); + while (pos != std::string::npos) { + path.insert(pos + 1, 1, '\\'); + pos = path.find('\\', pos + 2); } + return path; +} - file.insert(file.begin(), '"'); - fileName.insert(fileName.begin(), '"'); +void Lexer::SetFileDefines(const std::string& file) { + fs::path path = fs::canonical(file); - file.push_back('"'); - fileName.push_back('"'); + auto full_path = StringizePath(path); + auto name = StringizePath(path.filename()); - AddMacro("__FILE_PATH__", file.c_str()); - AddMacro("__FILE_NAME__", fileName.c_str()); + AddMacro("__FILE_PATH__", full_path.c_str()); + AddMacro("__FILE_NAME__", name.c_str()); } void Lexer::CheckLineEmpty(bool allow_semi) { @@ -225,24 +194,22 @@ Lexer::SynthesizeIncludePathToken() SkipLineWhitespace(); - char name[PATH_MAX]; + std::string name; int i = 0; while (true) { char c = peek(); if (c == close_c || c == '\0' || i >= (int)sizeof(name) - 1 || IsNewline(c)) break; - if (DIRSEP_CHAR != '/' && c == '/') { - name[i++] = DIRSEP_CHAR; + if (fs::path::preferred_separator != '/' && c == '/') { + name.push_back(fs::path::preferred_separator); advance(); } else { - name[i++] = advance(); + name.push_back(advance()); } } - while (i > 0 && name[i - 1] <= ' ') - i--; /* strip trailing whitespace */ - assert(i < (int)sizeof name); - name[i] = '\0'; /* zero-terminate the string */ + while (!name.empty() && name.back() == ' ') + name.pop_back(); if (close_c) { if (advance() != close_c) @@ -253,7 +220,7 @@ Lexer::SynthesizeIncludePathToken() if (!open_c) open_c = '"'; - tok->atom = cc_.atom(ke::StringPrintf("%c%s", open_c, name)); + tok->atom = cc_.atom(ke::StringPrintf("%c%s", open_c, name.c_str())); } /* ftoi @@ -2344,6 +2311,7 @@ void Lexer::EnterFile(std::shared_ptr&& sf, const token_pos_t& from) state_.pos = state_.start; state_.line_start = state_.pos; SkipUtf8Bom(); + SetFileDefines(state_.inpf->name()); tokens_on_line_ = 0; } diff --git a/compiler/lexer.h b/compiler/lexer.h index 4b5e91e04..193358a10 100644 --- a/compiler/lexer.h +++ b/compiler/lexer.h @@ -19,6 +19,8 @@ // 3. This notice may not be removed or altered from any source distribution. #pragma once +#include + #include #include #include @@ -310,7 +312,7 @@ class Lexer void Init(std::shared_ptr sf); void Start(); - bool PlungeFile(const char* name, int try_currentpath, int try_includepaths); + bool PlungeFile(const std::string& name, int try_currentpath, int try_includepaths); std::shared_ptr OpenFile(const std::string& name); bool NeedSemicolon(); void AddMacro(const char* pattern, const char* subst); @@ -367,10 +369,10 @@ class Lexer void LexStringLiteral(full_token_t* tok, int flags); void LexSymbol(full_token_t* tok, Atom* atom); bool MaybeHandleLineContinuation(); - bool PlungeQualifiedFile(const char* name); + bool PlungeQualifiedFile(const std::string& name); full_token_t* PushSynthesizedToken(TokenKind kind, const token_pos_t& pos); void SynthesizeIncludePathToken(); - void SetFileDefines(std::string file); + void SetFileDefines(const std::string& file); void EnterFile(std::shared_ptr&& fp, const token_pos_t& from); void FillTokenPos(token_pos_t* pos); void SkipLineWhitespace(); @@ -516,4 +518,6 @@ class Lexer bool caching_tokens_ = false; }; +std::string StringizePath(const std::filesystem::path& in_path); + } // namespace sp diff --git a/compiler/main.cpp b/compiler/main.cpp index b8562ad11..996b3276e 100644 --- a/compiler/main.cpp +++ b/compiler/main.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -85,13 +86,15 @@ using namespace ke; namespace sp { +namespace fs = std::filesystem; + int pc_tag_string = 0; int pc_tag_bool = 0; -static void setconfig(char* root); +static void setconfig(const char* root); static void setconstants(void); static void inst_datetime_defines(CompileContext& cc); -static void inst_binary_name(CompileContext& cc, std::string binfile); +static void inst_binary_name(CompileContext& cc, const std::string& binfile); enum { TEST_PLAIN, /* no parentheses */ @@ -261,29 +264,12 @@ int RunCompiler(int argc, char** argv, CompileContext& cc) { return retcode; } -static void -inst_binary_name(CompileContext& cc, std::string binfile) -{ - auto sepIndex = binfile.find_last_of(DIRSEP_CHAR); - - std::string binfileName = sepIndex == std::string::npos ? binfile : binfile.substr(sepIndex + 1); - - if (DIRSEP_CHAR == '\\') { - auto pos = binfile.find('\\'); - while (pos != std::string::npos) { - binfile.insert(pos + 1, 1, '\\'); - pos = binfile.find('\\', pos + 2); - } - } - - binfile.insert(binfile.begin(), '"'); - binfileName.insert(binfileName.begin(), '"'); - - binfile.push_back('"'); - binfileName.push_back('"'); +static void inst_binary_name(CompileContext& cc, const std::string& binfile) { + fs::path binfile_path(binfile); + fs::path binfile_name = binfile_path.filename(); - cc.lexer()->AddMacro("__BINARY_PATH__", binfile.c_str()); - cc.lexer()->AddMacro("__BINARY_NAME__", binfileName.c_str()); + cc.lexer()->AddMacro("__BINARY_PATH__", binfile_path.string().c_str()); + cc.lexer()->AddMacro("__BINARY_NAME__", binfile_name.string().c_str()); } static void @@ -311,21 +297,13 @@ inst_datetime_defines(CompileContext& cc) cc.lexer()->AddMacro("__TIME__", ltime); } -#if defined __EMSCRIPTEN__ -// Needed due to EM_ASM usage -__attribute__((noinline)) -#endif -static void -setconfig(char* root) -{ - char path[PATH_MAX]; - char *ptr, *base; - int len; - - /* add the default "include" directory */ +static fs::path GetProcessPath([[maybe_unused]] const char* root) { #if defined KE_WINDOWS - GetModuleFileNameA(NULL, path, PATH_MAX); + char path[MAX_PATH]; + GetModuleFileNameA(NULL, path, sizeof(path)); + return fs::path(path); #elif defined __EMSCRIPTEN__ + char path[PATH_MAX]; if (EM_ASM_INT( { if (ENVIRONMENT_IS_NODE) { @@ -338,43 +316,38 @@ setconfig(char* root) root != NULL) { SafeStrcpy(path, sizeof(path), root); } + return fs::path(path); #else - if (root != NULL) - SafeStrcpy(path, sizeof(path), root); /* path + filename (hopefully) */ + return fs::path(root); #endif +} - /* terminate just behind last \ or : */ - if ((ptr = strrchr(path, DIRSEP_CHAR)) != NULL || (ptr = strchr(path, ':')) != NULL) { - /* If there is no "\" or ":", the string probably does not contain the - * path; so we just don't add it to the list in that case - */ - *(ptr + 1) = '\0'; - base = ptr; - strcat(path, "include"); - len = strlen(path); - path[len] = DIRSEP_CHAR; - path[len + 1] = '\0'; - /* see if it exists */ - if (access(path, 0) != 0 && *base == DIRSEP_CHAR) { - /* There is no "include" directory below the directory where the compiler - * is found. This typically means that the compiler is in a "bin" sub-directory - * and the "include" is below the *parent*. So find the parent... - */ - *base = '\0'; - if ((ptr = strrchr(path, DIRSEP_CHAR)) != NULL) { - *(ptr + 1) = '\0'; - strcat(path, "include"); - len = strlen(path); - path[len] = DIRSEP_CHAR; - path[len + 1] = '\0'; - } else { - *base = DIRSEP_CHAR; - } - } +#if defined __EMSCRIPTEN__ +// Needed due to EM_ASM usage +__attribute__((noinline)) +#endif +static void setconfig(const char* root) { + fs::path proc_path = GetProcessPath(root); + + fs::path proc_dir = proc_path.parent_path(); + if (proc_dir.empty()) + return; - auto& cc = CompileContext::get(); - cc.options()->include_paths.emplace_back(path); + auto& cc = CompileContext::get(); + + fs::path include_dir = proc_dir / "include"; + if (!fs::is_directory(include_dir)) { + // There is no "include" directory below the directory where the compiler + // is found. This typically means that the compiler is in a "bin" sub-directory + // and the "include" is below the *parent*. So find the parent... + proc_dir = proc_dir.parent_path(); + if (proc_dir.empty()) + return; + include_dir = proc_dir / "include"; } + + if (fs::is_directory(include_dir)) + cc.options()->include_paths.emplace_back(include_dir.string()); } void setcaption() { diff --git a/compiler/parser.cpp b/compiler/parser.cpp index 58c195285..157a65155 100644 --- a/compiler/parser.cpp +++ b/compiler/parser.cpp @@ -151,7 +151,7 @@ Parser::Parse() if (!lexer_->need(tSYN_INCLUDE_PATH)) break; auto name = lexer_->current_token()->data(); - auto result = lexer_->PlungeFile(name.c_str() + 1, (name[0] != '<'), TRUE); + auto result = lexer_->PlungeFile(name.substr(1), (name[0] != '<'), TRUE); if (!result && tok != tpTRYINCLUDE) { report(417) << name.substr(1); cc_.set_must_abort(); @@ -223,7 +223,7 @@ void Parser::CreateInitialScopes(std::vector* stmts) { } if (!cc_.default_include().empty()) { - const char* incfname = cc_.default_include().c_str(); + 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)); diff --git a/compiler/sc.h b/compiler/sc.h index 0db17746f..00f7e40fb 100644 --- a/compiler/sc.h +++ b/compiler/sc.h @@ -64,13 +64,6 @@ namespace sp { '\x7f' /* termination character for preprocessor expressions (the "DEL" code) */ #define sDEF_PREFIX "sourcemod.inc" /* default prefix filename */ -#ifdef _WIN32 -static constexpr char DIRSEP_CHAR = '\\'; -# define PATH_MAX _MAX_PATH -#else -static constexpr char DIRSEP_CHAR = '/'; -#endif - struct DefaultArrayData; struct DefaultArg : public PoolObject {