diff --git a/lexilla/.gitignore b/lexilla/.gitignore
index a5292f2c5..635587318 100644
--- a/lexilla/.gitignore
+++ b/lexilla/.gitignore
@@ -22,7 +22,7 @@ __pycache__
*.suo
*.aps
*.sln
-*.vcxproj.*
+*.vcxproj.user
*.idb
*.bsc
*.intermediate.manifest
diff --git a/lexilla/cppcheck.suppress b/lexilla/cppcheck.suppress
index ed034fa8e..819050d17 100644
--- a/lexilla/cppcheck.suppress
+++ b/lexilla/cppcheck.suppress
@@ -36,10 +36,8 @@ constParameter:lexilla/lexers/LexCoffeeScript.cxx
constParameter:lexilla/lexers/LexCPP.cxx
variableScope:lexilla/lexers/LexCSS.cxx
variableScope:lexilla/lexers/LexDataflex.cxx
-knownConditionTrueFalse:lexilla/lexers/LexECL.cxx
variableScope:lexilla/lexers/LexErlang.cxx
knownConditionTrueFalse:lexilla/lexers/LexErlang.cxx
-knownConditionTrueFalse:lexilla/lexers/LexEScript.cxx
constParameter:lexilla/lexers/LexFortran.cxx
redundantCondition:lexilla/lexers/LexFSharp.cxx
knownConditionTrueFalse:lexilla/lexers/LexFSharp.cxx
@@ -91,9 +89,7 @@ constParameter:lexilla/lexers/LexTADS3.cxx
invalidscanf:lexilla/lexers/LexTCMD.cxx
constParameter:lexilla/lexers/LexTeX.cxx
variableScope:lexilla/lexers/LexTeX.cxx
-knownConditionTrueFalse:lexilla/lexers/LexTxt2tags.cxx
constParameter:lexilla/lexers/LexVerilog.cxx
-knownConditionTrueFalse:lexilla/lexers/LexVerilog.cxx
constParameter:lexilla/lexers/LexVHDL.cxx
shadowVariable:lexilla/lexers/LexVHDL.cxx
unreadVariable:lexilla/lexers/LexVHDL.cxx
@@ -141,3 +137,6 @@ redundantAssignment:lexilla/lexers/LexCPP.cxx
// Suppress everything in catch.hpp as won't be changing
*:lexilla/test/unit/catch.hpp
+// cppcheck gives up inside catch.hpp
+*:lexilla/test/unit/UnitTester.cxx
+*:lexilla/test/unit/unitTest.cxx
diff --git a/lexilla/doc/LexillaHistory.html b/lexilla/doc/LexillaHistory.html
index fe4008a49..303cadf2b 100644
--- a/lexilla/doc/LexillaHistory.html
+++ b/lexilla/doc/LexillaHistory.html
@@ -567,6 +567,7 @@
Bertrand Lacoste |
| Ivan Ustûžanin |
+ Rainer Kottenhoff |
Releases
@@ -581,7 +582,29 @@
Allow F# triple-quoted strings to interpolate string literals.
Issue #21.
-
+ -
+ Highlight F# printf specifiers in type-checked interpolated strings.
+ Issue #24.
+
+ -
+ Fix Markdown hang when document ends with "`", "*", "_", or similar.
+ Issue #23.
+
+ -
+ Treat '.' as an operator instead of part of a word for PHP.
+ Issue #22.
+ Bug #2225.
+
+ -
+ Check PHP numeric literals, showing invalid values with default style instead of numeric.
+ Issue #20.
+
+ -
+ For PHP, recognize start of comment after numeric literal.
+ Issue #20.
+ Bug #2143.
+
+
diff --git a/lexilla/lexers/LexHTML.cxx b/lexilla/lexers/LexHTML.cxx
index 902f25f53..104cec90f 100644
--- a/lexilla/lexers/LexHTML.cxx
+++ b/lexilla/lexers/LexHTML.cxx
@@ -524,6 +524,119 @@ bool isDjangoBlockEnd(const int ch, const int chNext, const std::string &blockTy
}
}
+class PhpNumberState {
+ enum NumberBase { BASE_10 = 0, BASE_2, BASE_8, BASE_16 };
+ static constexpr const char *const digitList[] = { "_0123456789", "_01", "_01234567", "_0123456789abcdefABCDEF" };
+
+ NumberBase base = BASE_10;
+ bool decimalPart = false;
+ bool exponentPart = false;
+ bool invalid = false;
+ bool finished = false;
+
+ bool leadingZero = false;
+ bool invalidBase8 = false;
+
+ bool betweenDigits = false;
+ bool decimalChar = false;
+ bool exponentChar = false;
+
+public:
+ inline bool isInvalid() { return invalid; }
+ inline bool isFinished() { return finished; }
+
+ bool init(int ch, int chPlus1, int chPlus2) {
+ base = BASE_10;
+ decimalPart = false;
+ exponentPart = false;
+ invalid = false;
+ finished = false;
+
+ leadingZero = false;
+ invalidBase8 = false;
+
+ betweenDigits = false;
+ decimalChar = false;
+ exponentChar = false;
+
+ if (ch == '.' && strchr(digitList[BASE_10] + !betweenDigits, chPlus1) != nullptr) {
+ decimalPart = true;
+ betweenDigits = true;
+ } else if (ch == '0' && (chPlus1 == 'b' || chPlus1 == 'B')) {
+ base = BASE_2;
+ } else if (ch == '0' && (chPlus1 == 'o' || chPlus1 == 'O')) {
+ base = BASE_8;
+ } else if (ch == '0' && (chPlus1 == 'x' || chPlus1 == 'X')) {
+ base = BASE_16;
+ } else if (strchr(digitList[BASE_10] + !betweenDigits, ch) != nullptr) {
+ leadingZero = ch == '0';
+ betweenDigits = true;
+ check(chPlus1, chPlus2);
+ if (finished && leadingZero) {
+ // single zero should be base 10
+ base = BASE_10;
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ bool check(int ch, int chPlus1) {
+ if (strchr(digitList[base] + !betweenDigits, ch) != nullptr) {
+ if (leadingZero) {
+ invalidBase8 = invalidBase8 || strchr(digitList[BASE_8] + !betweenDigits, ch) == nullptr;
+ }
+
+ betweenDigits = ch != '_';
+ decimalChar = false;
+ exponentChar = false;
+ } else if (ch == '_') {
+ invalid = true;
+
+ betweenDigits = false;
+ decimalChar = false;
+ // exponentChar is unchanged
+ } else if (base == BASE_10 && ch == '.' && (
+ !(decimalPart || exponentPart) || strchr(digitList[BASE_10] + !betweenDigits, chPlus1) != nullptr)
+ ) {
+ invalid = invalid || !betweenDigits || decimalPart || exponentPart;
+ decimalPart = true;
+
+ betweenDigits = false;
+ decimalChar = true;
+ exponentChar = false;
+ } else if (base == BASE_10 && (ch == 'e' || ch == 'E')) {
+ invalid = invalid || !(betweenDigits || decimalChar) || exponentPart;
+ exponentPart = true;
+
+ betweenDigits = false;
+ decimalChar = false;
+ exponentChar = true;
+ } else if (base == BASE_10 && (ch == '-' || ch == '+') && exponentChar) {
+ invalid = invalid || strchr(digitList[BASE_10] + !betweenDigits, chPlus1) == nullptr;
+
+ betweenDigits = false;
+ decimalChar = false;
+ // exponentChar is unchanged
+ } else if (IsPhpWordChar(ch)) {
+ invalid = true;
+
+ betweenDigits = false;
+ decimalChar = false;
+ exponentChar = false;
+ } else {
+ invalid = invalid || !(betweenDigits || decimalChar);
+ finished = true;
+ if (base == BASE_10 && leadingZero && !decimalPart && !exponentPart) {
+ base = BASE_8;
+ invalid = invalid || invalidBase8;
+ }
+ }
+ return finished;
+ }
+};
+
bool isPHPStringState(int state) {
return
(state == SCE_HPHP_HSTRING) ||
@@ -960,6 +1073,7 @@ void SCI_METHOD LexerHTML::Lex(Sci_PositionU startPos, Sci_Position length, int
}
styler.StartAt(startPos);
std::string prevWord;
+ PhpNumberState phpNumber;
std::string phpStringDelimiter;
int StateToPrint = initStyle;
int state = stateForPrintState(StateToPrint);
@@ -2273,7 +2387,7 @@ void SCI_METHOD LexerHTML::Lex(Sci_PositionU startPos, Sci_Position length, int
break;
///////////// start - PHP state handling
case SCE_HPHP_WORD:
- if (!IsAWordChar(ch)) {
+ if (!IsPhpWordChar(ch)) {
classifyWordHTPHP(styler.GetStartSegment(), i - 1, keywords5, styler);
if (ch == '/' && chNext == '*') {
i++;
@@ -2306,15 +2420,9 @@ void SCI_METHOD LexerHTML::Lex(Sci_PositionU startPos, Sci_Position length, int
}
break;
case SCE_HPHP_NUMBER:
- // recognize bases 8,10 or 16 integers OR floating-point numbers
- if (!IsADigit(ch)
- && strchr(".xXabcdefABCDEF_", ch) == NULL
- && ((ch != '-' && ch != '+') || (chPrev != 'e' && chPrev != 'E'))) {
- styler.ColourTo(i - 1, SCE_HPHP_NUMBER);
- if (IsOperator(ch))
- state = SCE_HPHP_OPERATOR;
- else
- state = SCE_HPHP_DEFAULT;
+ if (phpNumber.check(chNext, chNext2)) {
+ styler.ColourTo(i, phpNumber.isInvalid() ? SCE_HPHP_DEFAULT : SCE_HPHP_NUMBER);
+ state = SCE_HPHP_DEFAULT;
}
break;
case SCE_HPHP_VARIABLE:
@@ -2395,8 +2503,13 @@ void SCI_METHOD LexerHTML::Lex(Sci_PositionU startPos, Sci_Position length, int
case SCE_HPHP_OPERATOR:
case SCE_HPHP_DEFAULT:
styler.ColourTo(i - 1, StateToPrint);
- if (IsADigit(ch) || (ch == '.' && IsADigit(chNext))) {
- state = SCE_HPHP_NUMBER;
+ if (phpNumber.init(ch, chNext, chNext2)) {
+ if (phpNumber.isFinished()) {
+ styler.ColourTo(i, phpNumber.isInvalid() ? SCE_HPHP_DEFAULT : SCE_HPHP_NUMBER);
+ state = SCE_HPHP_DEFAULT;
+ } else {
+ state = SCE_HPHP_NUMBER;
+ }
} else if (IsAWordStart(ch)) {
state = SCE_HPHP_WORD;
} else if (ch == '/' && chNext == '*') {