From 868a398b8624e45bef67e4f73b86ba43f6e72dc3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 5 Mar 2023 19:15:43 +0100 Subject: [PATCH] Allow comparison operators of arbitrary length --- CHANGELOG.md | 1 + components/compiler/scanner.cpp | 155 +++++++++++++------------------- components/compiler/scanner.hpp | 26 +++--- 3 files changed, 81 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb25447f7..b55d5fae6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Bug #6645: Enemy block sounds align with animation instead of blocked hits Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes + Bug #6716: mwscript comparison operator handling is too restrictive Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6939: OpenMW-CS: ID columns are too short diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 9739241416..33a46a5753 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -10,6 +10,19 @@ #include +namespace +{ + bool startsComparisonOperator(const Compiler::MultiChar& c) + { + return c == '=' || c == '<' || c == '>' || c == '!'; + } + + bool validComparisonOperatorCharacter(const Compiler::MultiChar& c) + { + return startsComparisonOperator(c) || c == ' ' || c == '\t'; + } +} + namespace Compiler { bool Scanner::get(MultiChar& c) @@ -440,52 +453,68 @@ namespace Compiler special = S_member; } - else if (c == '=') + else if (startsComparisonOperator(c)) { - if (get(c)) + TokenLoc loc = mLoc; + std::string op; + c.appendTo(op); + while (get(c)) { - /// \todo hack to allow a space in comparison operators (add option to disable) - if (c == ' ' && !get(c)) - special = S_cmpEQ; - else if (c == '=') - special = S_cmpEQ; - else if (c == '>' || c == '<') // Treat => and =< as == - { - special = S_cmpEQ; - mErrorHandler.warning(std::string("invalid operator =") + c.data() + ", treating it as ==", mLoc); - } + if (validComparisonOperatorCharacter(c)) + c.appendTo(op); else { - special = S_cmpEQ; putback(c); - // return false; - /// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. + break; } } - else - { - putback(c); - return false; - } - } - else if (c == '!') - { - if (get(c)) + size_t end = op.size(); + while (end > 0 && (op[end - 1] == '\t' || op[end - 1] == ' ')) + --end; + std::string_view trimmedOperator = std::string_view{ op }.substr(0, end); + switch (trimmedOperator[0]) { - /// \todo hack to allow a space in comparison operators (add option to disable) - if (c == ' ' && !get(c)) - return false; - - if (c == '=') + case '=': + special = S_cmpEQ; + if (trimmedOperator.size() != 2 || trimmedOperator[1] != '=') + mErrorHandler.warning( + "invalid operator " + std::string(trimmedOperator) + ", treating it as ==", loc); + break; + case '<': + special = S_cmpLT; + if (trimmedOperator[1] == '=') + { + special = S_cmpLE; + if (trimmedOperator.size() != 2) + mErrorHandler.warning( + "invalid operator " + std::string(trimmedOperator) + ", treating it as <=", loc); + } + else if (trimmedOperator.size() != 1) + mErrorHandler.warning( + "invalid operator " + std::string(trimmedOperator) + ", treating it as <", loc); + break; + case '>': + special = S_cmpGT; + if (trimmedOperator[1] == '=') + { + special = S_cmpGE; + if (trimmedOperator.size() != 2) + mErrorHandler.warning( + "invalid operator " + std::string(trimmedOperator) + ", treating it as >=", loc); + } + else if (trimmedOperator.size() != 1) + mErrorHandler.warning( + "invalid operator " + std::string(trimmedOperator) + ", treating it as >", loc); + break; + case '!': special = S_cmpNE; - else - { - putback(c); + if (trimmedOperator.size() != 2 || trimmedOperator[1] != '=') + mErrorHandler.warning( + "invalid operator " + std::string(trimmedOperator) + ", treating it as !=", loc); + break; + default: return false; - } } - else - return false; } else if (c.isMinusSign()) { @@ -503,62 +532,6 @@ namespace Compiler else special = S_minus; } - else if (c == '<') - { - if (get(c)) - { - /// \todo hack to allow a space in comparison operators (add option to disable) - if (c == ' ' && !get(c)) - special = S_cmpLT; - else if (c == '=') - { - special = S_cmpLE; - - if (get(c) && c != '=') // <== is a allowed as an alternative to <= :( - putback(c); - } - else if (c == '<' || c == '>') // Treat <> and << as < - { - special = S_cmpLT; - mErrorHandler.warning("Invalid operator, treating it as <", mLoc); - } - else - { - putback(c); - special = S_cmpLT; - } - } - else - special = S_cmpLT; - } - else if (c == '>') - { - if (get(c)) - { - /// \todo hack to allow a space in comparison operators (add option to disable) - if (c == ' ' && !get(c)) - special = S_cmpGT; - else if (c == '=') - { - special = S_cmpGE; - - if (get(c) && c != '=') // >== is a allowed as an alternative to >= :( - putback(c); - } - else if (c == '<' || c == '>') // Treat >< and >> as > - { - special = S_cmpGT; - mErrorHandler.warning("Invalid operator, treating it as >", mLoc); - } - else - { - putback(c); - special = S_cmpGT; - } - } - else - special = S_cmpGT; - } else if (c == '+') special = S_plus; else if (c == '*') diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 354ad9ebf1..d5cea61e7f 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -48,25 +48,31 @@ namespace Compiler return -1; } - bool operator==(const char ch) { return mData[0] == ch && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; } + bool operator==(const char ch) const + { + return mData[0] == ch && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; + } - bool operator==(const MultiChar& ch) + bool operator==(const MultiChar& ch) const { return mData[0] == ch.mData[0] && mData[1] == ch.mData[1] && mData[2] == ch.mData[2] && mData[3] == ch.mData[3]; } - bool operator!=(const char ch) { return mData[0] != ch || mData[1] != 0 || mData[2] != 0 || mData[3] != 0; } + bool operator!=(const char ch) const + { + return mData[0] != ch || mData[1] != 0 || mData[2] != 0 || mData[3] != 0; + } - bool isWhitespace() + bool isWhitespace() const { return (mData[0] == ' ' || mData[0] == '\t' || mData[0] == ',') && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; } - bool isDigit() { return std::isdigit(mData[0]) && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; } + bool isDigit() const { return std::isdigit(mData[0]) && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; } - bool isMinusSign() + bool isMinusSign() const { if (mData[0] == '-' && mData[1] == 0 && mData[2] == 0 && mData[3] == 0) return true; @@ -74,7 +80,7 @@ namespace Compiler return mData[0] == '\xe2' && mData[1] == '\x80' && mData[2] == '\x93' && mData[3] == 0; } - bool isAlpha() + bool isAlpha() const { if (isMinusSign()) return false; @@ -82,13 +88,13 @@ namespace Compiler return std::isalpha(mData[0]) || mData[1] != 0 || mData[2] != 0 || mData[3] != 0; } - void appendTo(std::string& str) + void appendTo(std::string& str) const { for (int i = 0; i <= mLength; i++) str += mData[i]; } - void putback(std::istream& in) + void putback(std::istream& in) const { for (int i = mLength; i >= 0; i--) in.putback(mData[i]); @@ -157,7 +163,7 @@ namespace Compiler mLength = -1; } - std::string data() + std::string data() const { // NB: mLength is the number of the last element in the array return std::string(mData, mLength + 1);