mirror of
https://github.com/rizonesoft/Notepad3.git
synced 2026-06-11 21:03:05 +08:00
Merge pull request #5914 from RaiKoHoff/dev_master
feat(tinyexpr): render logical results as "true"/"false"
This commit is contained in:
commit
38187a1fd1
106
lexilla/np3_patches/003_LexMarkdown_AtTermStart.md
Normal file
106
lexilla/np3_patches/003_LexMarkdown_AtTermStart.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Patch 003: LexMarkdown — AtTermStart accepts opening punctuation
|
||||
|
||||
**File:** `lexilla/lexers/LexMarkdown.cxx`
|
||||
**Status:** NP3 local fix (candidate for upstream submission)
|
||||
**First applied:** 2026-05-21
|
||||
|
||||
## Problem
|
||||
|
||||
The stock Lexilla Markdown lexer fails to recognise inline spans that
|
||||
open immediately after common opening punctuation. The user-reported
|
||||
case is inline code:
|
||||
|
||||
```
|
||||
(`Hello`) <- not highlighted
|
||||
( `Hello`) <- highlighted (extra space)
|
||||
```
|
||||
|
||||
The same defect rejects:
|
||||
|
||||
```
|
||||
[`x`] {`x`} <`x`> "`x`" '`x`'
|
||||
(**x**) [**x**] (~~x~~) (*x*) (_x_)
|
||||
```
|
||||
|
||||
VS Code and other CommonMark-compliant renderers accept all of these.
|
||||
Per CommonMark:
|
||||
|
||||
- **Code spans** have no left-flank restriction whatsoever.
|
||||
- **Emphasis / strong** opening uses left-flanking-delimiter-run rules;
|
||||
a delimiter preceded by Unicode punctuation and followed by a
|
||||
non-whitespace non-punctuation char is a valid left flank.
|
||||
|
||||
## Root cause
|
||||
|
||||
`AtTermStart` (helper used by `IsCompleteStyleRegion` and the multi-
|
||||
backtick code-span entry point) only returns `true` when `chPrev` is
|
||||
whitespace or start-of-file:
|
||||
|
||||
```cpp
|
||||
bool AtTermStart(const StyleContext &sc) noexcept {
|
||||
return sc.currentPos == 0 || sc.chPrev == 0 || isspacechar(sc.chPrev);
|
||||
}
|
||||
```
|
||||
|
||||
With `chPrev = '('`, the guard rejects the opening backtick and the
|
||||
span never enters `SCE_MARKDOWN_CODE`/`SCE_MARKDOWN_CODE2`. Same for
|
||||
`SCE_MARKDOWN_STRONG{1,2}`, `SCE_MARKDOWN_EM{1,2}`, and
|
||||
`SCE_MARKDOWN_STRIKEOUT`.
|
||||
|
||||
## Fix
|
||||
|
||||
Extend `AtTermStart` to additionally accept `(`, `[`, `{`, `<`, `"`,
|
||||
`'` as valid left-edge characters. This covers all bracketed and
|
||||
quoted forms users typically write, without loosening behaviour for
|
||||
mid-word delimiters (`foo*bar*` remains unchanged — `chPrev = 'o'` is
|
||||
still rejected).
|
||||
|
||||
```cpp
|
||||
bool AtTermStart(const StyleContext &sc) noexcept {
|
||||
if (sc.currentPos == 0 || sc.chPrev == 0 || isspacechar(sc.chPrev))
|
||||
return true;
|
||||
switch (sc.chPrev) {
|
||||
case '(': case '[': case '{': case '<':
|
||||
case '"': case '\'':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Scope
|
||||
|
||||
Affects all inline tokens gated by `IsCompleteStyleRegion`:
|
||||
|
||||
- `` ` `` (single-backtick code)
|
||||
- `` `` `` `` (multi-backtick code, via direct `AtTermStart(sc)` call)
|
||||
- `**` / `__` (strong)
|
||||
- `*` / `_` (emphasis)
|
||||
- `~~` (strikeout)
|
||||
|
||||
Block-level constructs (headers, lists, code blocks, blockquote,
|
||||
hrule, links) are untouched.
|
||||
|
||||
## Visual verification
|
||||
|
||||
Open `test/test_files/StyleLexers/styleLexMARKDOWN/README.md` in
|
||||
Notepad3 after build. The section appended for this patch demonstrates
|
||||
each adjacency variant — inline code/strong/em/strikeout following
|
||||
every opener should colour correctly.
|
||||
|
||||
## Upstream
|
||||
|
||||
This is an upstream Lexilla defect. The fix is small and self-
|
||||
contained; a PR against ScintillaOrg/lexilla would be welcome. Until
|
||||
then, keep this patch.
|
||||
|
||||
## Upgrade procedure
|
||||
|
||||
After a Lexilla upgrade:
|
||||
|
||||
1. Diff `lexilla/lexers/LexMarkdown.cxx` against the upstream copy.
|
||||
2. Reapply this patch (`003_LexMarkdown_AtTermStart.patch`) if the
|
||||
upstream still ships the whitespace-only `AtTermStart`.
|
||||
3. If upstream has integrated the fix, retire this patch and remove
|
||||
its row from `README.md`.
|
||||
27
lexilla/np3_patches/003_LexMarkdown_AtTermStart.patch
Normal file
27
lexilla/np3_patches/003_LexMarkdown_AtTermStart.patch
Normal file
@ -0,0 +1,27 @@
|
||||
diff --git a/lexilla/lexers/LexMarkdown.cxx b/lexilla/lexers/LexMarkdown.cxx
|
||||
index 667b3b534..f58dab187 100644
|
||||
--- a/lexilla/lexers/LexMarkdown.cxx
|
||||
+++ b/lexilla/lexers/LexMarkdown.cxx
|
||||
@@ -119,7 +119,21 @@ bool HasPrevLineContent(StyleContext &sc) {
|
||||
}
|
||||
|
||||
bool AtTermStart(const StyleContext &sc) noexcept {
|
||||
- return sc.currentPos == 0 || sc.chPrev == 0 || isspacechar(sc.chPrev);
|
||||
+ if (sc.currentPos == 0 || sc.chPrev == 0 || isspacechar(sc.chPrev))
|
||||
+ return true;
|
||||
+ // NP3 patch: also accept common opening punctuation so inline spans
|
||||
+ // (code, strong, emphasis, strikeout) can open directly after them,
|
||||
+ // e.g. (`x`), [`x`], {`x`}, <`x`>, "`x`", '`x`'. CommonMark and VS
|
||||
+ // Code accept these — code spans have no left-flank restriction at
|
||||
+ // all, and emphasis after opening punctuation is a valid
|
||||
+ // left-flanking delimiter run.
|
||||
+ switch (sc.chPrev) {
|
||||
+ case '(': case '[': case '{': case '<':
|
||||
+ case '"': case '\'':
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
}
|
||||
|
||||
bool IsCompleteStyleRegion(StyleContext &sc, const char *token) {
|
||||
@ -313,9 +313,9 @@ SQRT(3^2 + 4^2)=? → 5
|
||||
### Unit Conversions
|
||||
|
||||
```
|
||||
72 * 0.0254=? → 1.8288 (72 inches to meters)
|
||||
100 / 2.54=? → 39.3701 (100 cm to inches)
|
||||
(98.6 - 32) * 5/9=? → 37 (Fahrenheit to Celsius)
|
||||
72 * 0.0254=? → 1.8288 (72 inches to meters)
|
||||
100 / 2.54=? → 39.3700787401575 (100 cm to inches)
|
||||
(98.6 - 32) * 5/9=? → 37 (Fahrenheit to Celsius)
|
||||
```
|
||||
|
||||
### Programming Helpers
|
||||
@ -361,7 +361,7 @@ TinyExpr++ in Notepad3 automatically adapts to the **decimal separator** of the
|
||||
| **Function argument separator** | `,` (comma) | `;` (semicolon) |
|
||||
| **Number example** | `3.14` | `3,14` |
|
||||
| **Function call** | `SUM(1.5, 2)` | `SUM(1,5; 2)` |
|
||||
| **Inline evaluation** | `1/3=?` → `0.33333333` | `1/3=?` → `0,33333333` |
|
||||
| **Inline evaluation** | `1/3=?` → `0.333333333333333` | `1/3=?` → `0,333333333333333` |
|
||||
|
||||
### Examples by Locale
|
||||
|
||||
@ -383,6 +383,77 @@ IF(2,5 > 1; 10; 20)=? → 10
|
||||
|
||||
---
|
||||
|
||||
## C API: Boolean-Aware Evaluation (developer reference)
|
||||
|
||||
The C wrapper around TinyExpr++ exposes an evaluator that renders results
|
||||
of boolean-looking expressions as the words `true` / `false`, so callers
|
||||
don't have to invent their own classification scheme:
|
||||
|
||||
```c
|
||||
#include "tinyexpr_cif.h"
|
||||
|
||||
const char *te_interp_str(const char *expression, te_int_t *error);
|
||||
```
|
||||
|
||||
The function evaluates the expression once and returns a thread-local
|
||||
internal buffer. The returned pointer remains valid until the next call
|
||||
from the same thread; `*error` follows the same convention as `te_interp()`
|
||||
(`0` on success, 1-based parse-error position on failure).
|
||||
|
||||
| Returned string | When |
|
||||
|-----------------|------|
|
||||
| `"true"` / `"false"` | Expression is lexically logical **and** evaluates to exactly `1.0` / `0.0`. |
|
||||
| `"nan"`, `"inf"`, `"-inf"` | Result is non-finite (e.g., `0/0`, `LN(0)`, comparison involving `NaN`). |
|
||||
| Integer-like (`%.21g`) | Finite value whose fractional part is below `1e-15` and whose magnitude is below `1e21`. |
|
||||
| Decimal (`%.15g`) | All other finite results. |
|
||||
|
||||
The numeric formatting mirrors Notepad3's `TinyExprToStringA` exactly, so values returned by `te_interp_str()` match what the status bar / `=?` inline-replacement would render. Hex / binary output modes are UI-level concerns and remain in `TinyExprToStringA` proper.
|
||||
|
||||
### When is an expression classified as logical?
|
||||
|
||||
Classification requires **both** of the following to hold:
|
||||
|
||||
1. **Lexical hit** at parenthesis depth 0 (one fully-enclosing outer
|
||||
pair is stripped first, so `(1==1)` is treated like `1==1`):
|
||||
- a relational / equality / logical operator: `==`, `=`, `!=`, `<>`,
|
||||
`<=`, `>=`, `<`, `>`, `&&`, `||`, leading `!`
|
||||
- the bare keywords `true` / `false`
|
||||
- an outermost call to `AND`, `OR`, `NOT`, `ISERR`, `ISERROR`,
|
||||
`ISNA`, `ISNAN`, `ISEVEN`, or `ISODD` (case-insensitive)
|
||||
2. **Value hit**: the finite evaluation result is exactly `0.0` or `1.0`.
|
||||
|
||||
Bit-shift (`<<`, `>>`) and bit-rotate (`<<<`, `>>>`) are consumed by the
|
||||
scanner without triggering classification. Block comments (`/* … */`) and
|
||||
line comments (`// …`) are skipped, mirroring the parser.
|
||||
|
||||
`IF` / `IFS` are intentionally **not** in the predicate list — they return
|
||||
arbitrary user-supplied values, so `IF(a>b, 5, 10)` is numeric, not
|
||||
boolean.
|
||||
|
||||
### Examples
|
||||
|
||||
| Expression | Returns | Reason |
|
||||
|------------|---------|--------|
|
||||
| `1+1=2+2` | `"false"` | Parses as `(1+1) == (2+2)`; lone `=` is equality. |
|
||||
| `1+1=2` | `"true"` | Same path; `2 == 2` is true. |
|
||||
| `(1==1)` | `"true"` | Outer parens stripped before the lexical scan. |
|
||||
| `ISEVEN(4)` | `"true"` | Top-level call to a predicate function. |
|
||||
| `1 && 0` | `"false"` | Logical AND at depth 0. |
|
||||
| `!0` | `"true"` | Unary logical-NOT. |
|
||||
| `IF(1>2, 100, 200)` | `"200"` | `IF` is not a predicate; `>` is inside parens. |
|
||||
| `1 + (1==1)` | `"2"` | `==` is inside parens, and the result isn't 0/1. |
|
||||
| `(1==1) * (2==2)` | `"1"` | No comparison at depth 0; result is arithmetic. |
|
||||
| `0/0 == 1` | `"nan"` | Non-finite result bypasses classification. |
|
||||
| `2*PI` | `"6.28318530717959"` | No logical operator; `%.15g` format. |
|
||||
|
||||
> **Why both checks?** A purely value-based test would mis-label `1+0` as
|
||||
> a boolean. A purely lexical test would mis-label `IF(a>b, 5, 10)`
|
||||
> (which evaluates to `5` or `10`, not `0`/`1`). The intersection is much
|
||||
> closer to user intent. `te_interp()` and `te_compile()` remain
|
||||
> unchanged for callers that prefer to handle classification themselves.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- All function names are **case-insensitive**.
|
||||
|
||||
@ -211,8 +211,9 @@ static bool s_bUndoRedoScroll = false;
|
||||
// Auto-scroll state moved to Notepad3Util.c
|
||||
|
||||
// for tiny expression calculation
|
||||
static double s_dExpression = 0.0;
|
||||
static te_int_t s_iExprError = -1;
|
||||
static double s_dExpression = 0.0;
|
||||
static te_int_t s_iExprError = -1;
|
||||
static bool s_bExprIsLogical = false;
|
||||
|
||||
// TinyExpr++ output mode (process-local, cycled by double-click on STATUS_TINYEXPR)
|
||||
typedef enum TE_OUT_MODE_T {
|
||||
@ -614,9 +615,9 @@ static inline void ResetFileObservationData(const bool bResetEvt) {
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#define TE_ZERO (1.0E-8)
|
||||
#define TE_FMTA "%.8G"
|
||||
#define TE_FMTW L"%.8G"
|
||||
#define TE_ZERO (1.0E-15)
|
||||
#define TE_FMTA "%.15g"
|
||||
#define TE_FMTW L"%.15g"
|
||||
|
||||
// Bounds for safe double -> signed-integer cast. (double)INT_MAX/INT64_MAX may
|
||||
// round UP to the next power of two, so use literals strictly below 2^31 / 2^63.
|
||||
@ -694,8 +695,14 @@ static void _FormatBinW(LPWSTR pszDest, size_t cchDest, unsigned __int64 u, int
|
||||
pszDest[pos] = L'\0';
|
||||
}
|
||||
|
||||
void TinyExprToStringA(LPSTR pszDest, size_t cchDest, const double dExprEval)
|
||||
void TinyExprToStringA(LPSTR pszDest, size_t cchDest, const double dExprEval, const bool bIsLogical)
|
||||
{
|
||||
// Logical expression with a finite 0/1 result -> render as true/false,
|
||||
// overriding hex/binary output modes (booleans are not a numeric value).
|
||||
if (bIsLogical && isfinite(dExprEval) && (dExprEval == 0.0 || dExprEval == 1.0)) {
|
||||
StringCchCopyA(pszDest, cchDest, (dExprEval == 1.0) ? "true" : "false");
|
||||
return;
|
||||
}
|
||||
if ((s_iTinyExprOutMode != TE_OUT_DEC) && isfinite(dExprEval) && (fabs(dExprEval) < _TinyExprIntBound())) {
|
||||
int const width = _TinyExprBitWidth();
|
||||
__int64 const i64 = (__int64)llround(dExprEval);
|
||||
@ -717,7 +724,7 @@ void TinyExprToStringA(LPSTR pszDest, size_t cchDest, const double dExprEval)
|
||||
double intpart = 0.0;
|
||||
double const fracpart = modf(dExprEval, &intpart);
|
||||
if ((fabs(fracpart) < TE_ZERO) && (fabs(intpart) < 1.0E+21)) {
|
||||
StringCchPrintfA(pszDest, cchDest, "%.21G", intpart); // integer full number display
|
||||
StringCchPrintfA(pszDest, cchDest, "%.21g", intpart); // integer full number display
|
||||
}
|
||||
else {
|
||||
StringCchPrintfA(pszDest, cchDest, TE_FMTA, dExprEval);
|
||||
@ -725,8 +732,12 @@ void TinyExprToStringA(LPSTR pszDest, size_t cchDest, const double dExprEval)
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void TinyExprToString(LPWSTR pszDest, size_t cchDest, const double dExprEval)
|
||||
void TinyExprToString(LPWSTR pszDest, size_t cchDest, const double dExprEval, const bool bIsLogical)
|
||||
{
|
||||
if (bIsLogical && isfinite(dExprEval) && (dExprEval == 0.0 || dExprEval == 1.0)) {
|
||||
StringCchCopy(pszDest, cchDest, (dExprEval == 1.0) ? L"true" : L"false");
|
||||
return;
|
||||
}
|
||||
if ((s_iTinyExprOutMode != TE_OUT_DEC) && isfinite(dExprEval) && (fabs(dExprEval) < _TinyExprIntBound())) {
|
||||
int const width = _TinyExprBitWidth();
|
||||
__int64 const i64 = (__int64)llround(dExprEval);
|
||||
@ -748,7 +759,7 @@ void TinyExprToString(LPWSTR pszDest, size_t cchDest, const double dExprEval)
|
||||
double intpart = 0.0;
|
||||
double const fracpart = modf(dExprEval, &intpart);
|
||||
if ((fabs(fracpart) < TE_ZERO) && (fabs(intpart) < 1.0E+21)) {
|
||||
StringCchPrintf(pszDest, cchDest, L"%.21G", intpart); // integer full number display
|
||||
StringCchPrintf(pszDest, cchDest, L"%.21g", intpart); // integer full number display
|
||||
}
|
||||
else {
|
||||
StringCchPrintf(pszDest, cchDest, TE_FMTW, dExprEval);
|
||||
@ -767,7 +778,7 @@ static VOID CALLBACK TinyExprCopyTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEven
|
||||
|
||||
char chExpr[80] = { '\0' };
|
||||
if (s_iExprError == 0) {
|
||||
TinyExprToStringA(chExpr, COUNTOF(chExpr), s_dExpression);
|
||||
TinyExprToStringA(chExpr, COUNTOF(chExpr), s_dExpression, s_bExprIsLogical);
|
||||
} else if (s_iExprError > 0) {
|
||||
StringCchPrintfA(chExpr, COUNTOF(chExpr), "%s^[" TE_INT_FMT "]",
|
||||
s_pszTinyExprModePrefixA[s_iTinyExprOutMode], s_iExprError);
|
||||
@ -3001,8 +3012,14 @@ static bool _EvalTinyExpr(bool qmark)
|
||||
|
||||
double dExprEval = 0.0;
|
||||
te_int_t exprErr = 1;
|
||||
bool bExprIsLogical = false;
|
||||
while (*p && exprErr) {
|
||||
dExprEval = te_interp(p, &exprErr);
|
||||
if (!exprErr) {
|
||||
// `p` hasn't moved yet (the advance happens in the inner
|
||||
// while below); safe to classify the just-evaluated text.
|
||||
bExprIsLogical = (te_is_logical_expr(p) != 0);
|
||||
}
|
||||
// proceed to next possible expression
|
||||
while (*++p && exprErr && !(te_is_num(p) || te_is_op(p))) {}
|
||||
}
|
||||
@ -3010,7 +3027,7 @@ static bool _EvalTinyExpr(bool qmark)
|
||||
|
||||
if (!exprErr) {
|
||||
char chExpr[80] = { '\0' };
|
||||
TinyExprToStringA(chExpr, COUNTOF(chExpr), dExprEval);
|
||||
TinyExprToStringA(chExpr, COUNTOF(chExpr), dExprEval, bExprIsLogical);
|
||||
SciCall_ReplaceSel("");
|
||||
SciCall_SetSel(posBegin, posSelStart);
|
||||
SciCall_ReplaceSel(chExpr);
|
||||
@ -11526,7 +11543,8 @@ static void _UpdateStatusbarDelayed(bool bForceRedraw)
|
||||
if (g_iStatusbarVisible[STATUS_TINYEXPR]) {
|
||||
static WCHAR tchExpression[80] = { L'\0' }; // fits "0b" + 64 bits + NUL with headroom
|
||||
static te_int_t s_iExErr = -3;
|
||||
s_dExpression = 0.0;
|
||||
s_dExpression = 0.0;
|
||||
s_bExprIsLogical = false;
|
||||
StringCchPrintf(tchExpression, COUNTOF(tchExpression), L"%s--",
|
||||
s_pszTinyExprModePrefixW[s_iTinyExprOutMode]);
|
||||
|
||||
@ -11546,11 +11564,15 @@ static void _UpdateStatusbarDelayed(bool bForceRedraw)
|
||||
WideCharToMultiByte(1252, (WC_COMPOSITECHECK | WC_DISCARDNS), wchSelBuf, -1, chSeBuf, LARGE_BUFFER, &defchar, NULL);
|
||||
StrDelChrA(chSeBuf, chr_currency);
|
||||
|
||||
s_dExpression = te_interp(chSeBuf, &s_iExprError);
|
||||
s_dExpression = te_interp(chSeBuf, &s_iExprError);
|
||||
s_bExprIsLogical = (s_iExprError == 0) && (te_is_logical_expr(chSeBuf) != 0);
|
||||
} else {
|
||||
s_iExprError = -1;
|
||||
}
|
||||
} else if (Sci_IsMultiOrRectangleSelection() && !bIsSelectionEmpty) {
|
||||
// Multi-/rect-selection concatenates fragments into a synthesized
|
||||
// expression; the user-typed source isn't preserved, so don't
|
||||
// try to classify it as logical.
|
||||
s_dExpression = _InterpMultiSelectionTinyExpr(&s_iExprError);
|
||||
} else {
|
||||
s_iExprError = -2;
|
||||
@ -11560,7 +11582,7 @@ static void _UpdateStatusbarDelayed(bool bForceRedraw)
|
||||
}
|
||||
|
||||
if (!s_iExprError) {
|
||||
TinyExprToString(tchExpression, COUNTOF(tchExpression), s_dExpression);
|
||||
TinyExprToString(tchExpression, COUNTOF(tchExpression), s_dExpression, s_bExprIsLogical);
|
||||
} else if (s_iExprError > 0) {
|
||||
StringCchPrintf(tchExpression, COUNTOF(tchExpression), L"%s^[" _W(TE_INT_FMT) L"]",
|
||||
s_pszTinyExprModePrefixW[s_iTinyExprOutMode], s_iExprError);
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
#include "tinyexpr.h" // C++ TinyExpr++ header
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <clocale>
|
||||
#include <cmath>
|
||||
@ -134,6 +136,212 @@ static std::string te_cif_rewrite_binary_literals(const char *expression)
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Boolean-expression detection (for te_interp_str)
|
||||
//
|
||||
// An expression is classified as "logical" when, at parenthesis depth 0,
|
||||
// it contains any of:
|
||||
// - relational / equality / logical operators:
|
||||
// `==` `=` `!=` `<>` `<=` `>=` `<` `>` `&&` `||` `!` (unary or `!=`)
|
||||
// - the bare keywords `true` / `false`
|
||||
// - an outermost call to AND, OR, NOT, ISERR, ISERROR, ISNA, ISNAN,
|
||||
// ISEVEN, ISODD
|
||||
// IF / IFS are intentionally excluded - they return arbitrary user values
|
||||
// (`IF(a>b, 5, 10)` is not a boolean expression even though `a>b` is).
|
||||
//
|
||||
// Block / line comments are skipped. Bit-shift `<<`, `>>` and bit-rotate
|
||||
// `<<<`, `>>>` are explicitly consumed without triggering classification.
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace {
|
||||
|
||||
constexpr std::array<const char *, 9> kLogicalFunctions = {
|
||||
"and", "or", "not",
|
||||
"iserr", "iserror", "isna", "isnan",
|
||||
"iseven", "isodd",
|
||||
};
|
||||
|
||||
inline bool te_is_ident_char(unsigned char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_' || c == '.';
|
||||
}
|
||||
|
||||
inline char te_ascii_tolower(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') ? static_cast<char>(c + 32) : c;
|
||||
}
|
||||
|
||||
// Case-insensitive ASCII match of `kw` (lowercase) against expr[pos..].
|
||||
// Returns the matched length (> 0) on success and only if the byte just
|
||||
// past the match is NOT an identifier continuation. Returns 0 on no match.
|
||||
size_t te_match_ci(const std::string &expr, size_t pos, const char *kw)
|
||||
{
|
||||
size_t i = 0;
|
||||
while (kw[i]) {
|
||||
if (pos + i >= expr.size() || te_ascii_tolower(expr[pos + i]) != kw[i]) {
|
||||
return 0;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (pos + i < expr.size() &&
|
||||
te_is_ident_char(static_cast<unsigned char>(expr[pos + i]))) {
|
||||
return 0;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool te_is_logical_keyword_at(const std::string &expr, size_t pos)
|
||||
{
|
||||
return te_match_ci(expr, pos, "true") > 0 || te_match_ci(expr, pos, "false") > 0;
|
||||
}
|
||||
|
||||
bool te_is_logical_func_at(const std::string &expr, size_t pos)
|
||||
{
|
||||
for (const char *name : kLogicalFunctions) {
|
||||
size_t const consumed = te_match_ci(expr, pos, name);
|
||||
if (consumed == 0) {
|
||||
continue;
|
||||
}
|
||||
size_t after = pos + consumed;
|
||||
while (after < expr.size() && (expr[after] == ' ' || expr[after] == '\t')) {
|
||||
++after;
|
||||
}
|
||||
if (after < expr.size() && expr[after] == '(') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip whitespace and any fully-enclosing outer parenthesis pairs so that
|
||||
// `(1 == 1)` is classified the same as `1 == 1`.
|
||||
std::string te_strip_outer_parens(std::string s)
|
||||
{
|
||||
auto trim = [](std::string &t) {
|
||||
size_t a = 0;
|
||||
while (a < t.size() && std::isspace(static_cast<unsigned char>(t[a]))) ++a;
|
||||
size_t b = t.size();
|
||||
while (b > a && std::isspace(static_cast<unsigned char>(t[b - 1]))) --b;
|
||||
t.assign(t, a, b - a);
|
||||
};
|
||||
trim(s);
|
||||
while (s.size() >= 2 && s.front() == '(' && s.back() == ')') {
|
||||
// Find the close-paren that balances the leading '('. If it isn't
|
||||
// at the very end, the outer parens don't fully wrap (e.g.
|
||||
// "(a)+(b)") - or the parens are unbalanced - so stop.
|
||||
int depth = 0;
|
||||
size_t closePos = std::string::npos;
|
||||
for (size_t i = 0; i < s.size(); ++i) {
|
||||
if (s[i] == '(') {
|
||||
++depth;
|
||||
}
|
||||
else if (s[i] == ')' && --depth == 0) {
|
||||
closePos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (closePos != s.size() - 1) {
|
||||
break;
|
||||
}
|
||||
s.assign(s, 1, s.size() - 2);
|
||||
trim(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool te_expression_is_logical(const std::string &raw)
|
||||
{
|
||||
const std::string expr = te_strip_outer_parens(raw);
|
||||
int depth = 0;
|
||||
bool after_ident = false;
|
||||
const size_t n = expr.size();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const char c = expr[i];
|
||||
const char nxt = (i + 1 < n) ? expr[i + 1] : '\0';
|
||||
|
||||
// Skip C/C++ comments (mirrors TinyExpr++ parser behavior).
|
||||
if (c == '/' && nxt == '*') {
|
||||
size_t e = expr.find("*/", i + 2);
|
||||
i = (e == std::string::npos) ? n - 1 : e + 1;
|
||||
after_ident = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && nxt == '/') {
|
||||
size_t e = expr.find_first_of("\r\n", i + 2);
|
||||
i = (e == std::string::npos) ? n - 1 : e - 1;
|
||||
after_ident = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '(') {
|
||||
++depth;
|
||||
after_ident = false;
|
||||
continue;
|
||||
}
|
||||
if (c == ')') {
|
||||
if (depth > 0) {
|
||||
--depth;
|
||||
}
|
||||
after_ident = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (depth == 0) {
|
||||
// `<<`, `>>` (shift) and `<<<`, `>>>` (rotate) are NOT boolean
|
||||
// producers; consume and keep scanning. Lone `<` / `>` and the
|
||||
// `<=`, `>=`, `<>` variants DO classify as logical and fall
|
||||
// through to the return below.
|
||||
if ((c == '<' || c == '>') && c == nxt) {
|
||||
++i;
|
||||
if (i + 1 < n && expr[i + 1] == c) {
|
||||
++i;
|
||||
}
|
||||
after_ident = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Relational / equality / logical operators.
|
||||
if (c == '=' || c == '<' || c == '>' || c == '!') {
|
||||
return true;
|
||||
}
|
||||
if ((c == '&' && nxt == '&') || (c == '|' && nxt == '|')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Identifier start: check for boolean keyword / boolean function call.
|
||||
if (!after_ident && te_is_ident_char(static_cast<unsigned char>(c)) &&
|
||||
!(c >= '0' && c <= '9')) {
|
||||
if (te_is_logical_keyword_at(expr, i) ||
|
||||
te_is_logical_func_at(expr, i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
after_ident = te_is_ident_char(static_cast<unsigned char>(c));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mirrors Notepad3's TinyExprToStringA: integer-like values (fractional part
|
||||
// below 1e-15 and magnitude under 1e21) use `%.21g`; everything else -
|
||||
// including non-finite NaN / Inf / -Inf - falls through to `%.15g`, which
|
||||
// snprintf renders as "nan" / "inf" / "-inf". Hex / binary output modes are
|
||||
// UI-level concerns and remain in TinyExprToStringA proper.
|
||||
void te_format_number(char *buf, size_t bufSize, double v)
|
||||
{
|
||||
double intpart = 0.0;
|
||||
double const fracpart = std::modf(v, &intpart);
|
||||
if (std::fabs(fracpart) < 1.0E-15 && std::fabs(intpart) < 1.0E+21) {
|
||||
std::snprintf(buf, bufSize, "%.21g", intpart);
|
||||
}
|
||||
else {
|
||||
std::snprintf(buf, bufSize, "%.15g", v);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: map TinyExpr++ error state to old 1-based error position
|
||||
// Old convention: 0 = success, >= 1 = 1-based error position
|
||||
@ -166,13 +374,16 @@ double te_interp(const char *expression, te_int_t *error)
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
te_parser parser;
|
||||
te_cif_add_compat_functions(parser);
|
||||
te_cif_configure_separators(parser);
|
||||
std::string const rewritten = te_cif_rewrite_binary_literals(expression);
|
||||
double result;
|
||||
try {
|
||||
result = parser.evaluate(rewritten);
|
||||
te_parser parser;
|
||||
te_cif_add_compat_functions(parser);
|
||||
te_cif_configure_separators(parser);
|
||||
std::string const rewritten = te_cif_rewrite_binary_literals(expression);
|
||||
double const result = parser.evaluate(rewritten);
|
||||
if (error) {
|
||||
*error = map_error(parser);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (...) {
|
||||
if (error) {
|
||||
@ -180,11 +391,67 @@ double te_interp(const char *expression, te_int_t *error)
|
||||
}
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = map_error(parser);
|
||||
// Evaluates expression and returns a cooked string.
|
||||
// Returns "true" / "false" when the source is lexically logical AND the
|
||||
// result is finite and exactly 1.0 / 0.0; otherwise returns a numeric
|
||||
// formatting (or "nan" / "inf" / "-inf").
|
||||
const char *te_interp_str(const char *expression, te_int_t *error)
|
||||
{
|
||||
static thread_local char buf[64];
|
||||
constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
if (!expression || !*expression) {
|
||||
if (error) {
|
||||
*error = 1;
|
||||
}
|
||||
te_format_number(buf, sizeof(buf), kNaN);
|
||||
return buf;
|
||||
}
|
||||
|
||||
try {
|
||||
te_parser parser;
|
||||
te_cif_add_compat_functions(parser);
|
||||
te_cif_configure_separators(parser);
|
||||
std::string const rewritten = te_cif_rewrite_binary_literals(expression);
|
||||
double const result = parser.evaluate(rewritten);
|
||||
if (error) {
|
||||
*error = map_error(parser);
|
||||
}
|
||||
|
||||
if (parser.success() && std::isfinite(result) &&
|
||||
(result == 0.0 || result == 1.0) &&
|
||||
te_expression_is_logical(rewritten)) {
|
||||
std::snprintf(buf, sizeof(buf), "%s", result == 1.0 ? "true" : "false");
|
||||
return buf;
|
||||
}
|
||||
|
||||
te_format_number(buf, sizeof(buf), result);
|
||||
return buf;
|
||||
}
|
||||
catch (...) {
|
||||
if (error) {
|
||||
*error = 1;
|
||||
}
|
||||
te_format_number(buf, sizeof(buf), kNaN);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
// Lexical-only predicate: does this expression look logical?
|
||||
// See header for the full set of detected operators / keywords / functions.
|
||||
int te_is_logical_expr(const char *expression)
|
||||
{
|
||||
if (!expression || !*expression) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return te_expression_is_logical(std::string(expression)) ? 1 : 0;
|
||||
}
|
||||
catch (...) {
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Compiles an expression with bound variables. Returns NULL on error.
|
||||
|
||||
@ -41,6 +41,38 @@ typedef struct te_variable {
|
||||
* parse error on failure. */
|
||||
double te_interp(const char *expression, te_int_t *error);
|
||||
|
||||
/* Parses, evaluates, and returns a cooked string representation.
|
||||
* - "true" / "false" if the expression is "logical" AND the result
|
||||
* is finite and exactly 1.0 or 0.0.
|
||||
* - Numeric formatting otherwise, matching Notepad3's TinyExprToStringA:
|
||||
* near-integer values (fractional part below 1e-15 and magnitude
|
||||
* under 1e21) use `%.21g`, all other (and non-finite) values use
|
||||
* `%.15g`, which snprintf renders as "nan" / "inf" / "-inf" for
|
||||
* NaN / +Inf / -Inf respectively.
|
||||
*
|
||||
* "Logical" detection is lexical at parenthesis depth 0: the expression
|
||||
* is logical if it contains one of `==`, `=`, `!=`, `<>`, `<=`, `>=`,
|
||||
* `<`, `>`, `&&`, `||`, leading `!`, the bare keywords `true`/`false`,
|
||||
* or an outermost call to AND, OR, NOT, ISERR, ISERROR, ISNA, ISNAN,
|
||||
* ISEVEN, or ISODD (case-insensitive). IF / IFS are intentionally NOT
|
||||
* treated as logical since they return arbitrary user-supplied values.
|
||||
*
|
||||
* The returned pointer is to a thread-local internal buffer; it remains
|
||||
* valid until the next call from the same thread.
|
||||
*
|
||||
* *error follows the same convention as te_interp(): 0 on success,
|
||||
* 1-based parse error position on failure. */
|
||||
const char *te_interp_str(const char *expression, te_int_t *error);
|
||||
|
||||
/* Returns 1 if `expression` lexically looks like a logical/comparison
|
||||
* expression at parenthesis depth 0 (one fully-enclosing outer pair is
|
||||
* stripped first). Considered logical when it contains one of `==`, `=`,
|
||||
* `!=`, `<>`, `<=`, `>=`, `<`, `>`, `&&`, `||`, leading `!`, the bare
|
||||
* keywords `true` / `false`, or an outermost call to AND, OR, NOT,
|
||||
* ISERR, ISERROR, ISNA, ISNAN, ISEVEN, ISODD (case-insensitive).
|
||||
* Returns 0 otherwise. Does NOT evaluate the expression. */
|
||||
int te_is_logical_expr(const char *expression);
|
||||
|
||||
/* Parses the input expression and binds variables.
|
||||
* Returns NULL on error.
|
||||
* *error is set to 0 on success, or the 1-based error position on failure. */
|
||||
|
||||
391
test/test_files/calculation/tiny_expr.cpp
Normal file
391
test/test_files/calculation/tiny_expr.cpp
Normal file
@ -0,0 +1,391 @@
|
||||
/*
|
||||
* TinyExpr++ Expression Test File for Notepad3
|
||||
* =============================================
|
||||
*
|
||||
* How to use:
|
||||
* 1. Open this file in Notepad3.
|
||||
* 2. Enable Settings -> "Evaluate TinyExpr on Selection".
|
||||
* 3. For any test line: place the caret IMMEDIATELY after '=?' and press
|
||||
* ENTER (the trigger replaces '<expr>=?' inline with the result), OR
|
||||
* select the expression and read the status-bar TinyExpr field.
|
||||
* 4. Compare against the expected value shown in the trailing comment.
|
||||
*
|
||||
* Notes on output format (post-change):
|
||||
* * Numeric values use '%.15g' (decimal) and '%.21g' (integer-like, when
|
||||
* fractional part is below 1.0e-15 and magnitude is below 1.0e+21).
|
||||
* * Non-finite values render as 'nan' / 'inf' / '-inf' (lowercase).
|
||||
* * Boolean-detected expressions render as 'true' / 'false':
|
||||
* - Lexical hit at parenthesis depth 0 on one of:
|
||||
* == = != <> <= >= < > && || ! (or top-level call to
|
||||
* AND / OR / NOT / ISERR / ISERROR / ISNA / ISNAN / ISEVEN / ISODD,
|
||||
* or the bare keywords true / false)
|
||||
* AND
|
||||
* - Evaluated result is finite and exactly 0.0 or 1.0.
|
||||
* * Hex / binary status-bar output modes apply to NUMERIC results only;
|
||||
* boolean results override the mode and always show as 'true' / 'false'.
|
||||
*/
|
||||
|
||||
// Document-level commentary uses '//' or '/* ... */' C++-style
|
||||
// comments, which the TinyExpr++ parser also recognizes and strips.
|
||||
// Each test line is an expression terminated by '=?' (the inline-
|
||||
// evaluation trigger) followed by '// <expected-value>' so the
|
||||
// intended result stays visible after the trigger replaces '=?'.
|
||||
//
|
||||
// Opens in Notepad3 with the C/C++ lexer for syntax highlighting.
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 1. Basic arithmetic
|
||||
// ============================================================
|
||||
|
||||
1+1=? // 2
|
||||
10-3=? // 7
|
||||
6*7=? // 42
|
||||
20/4=? // 5
|
||||
10%3=? // 1 (modulus, not percent)
|
||||
2^10=? // 1024
|
||||
2**10=? // 1024 (alternative power syntax)
|
||||
-5+3=? // -2
|
||||
+5-3=? // 2
|
||||
1+2+3+4+5=? // 15
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 2. Operator precedence
|
||||
// ============================================================
|
||||
|
||||
5+5+5/2=? // 12.5 (division before addition)
|
||||
(5+5+5)/2=? // 7.5
|
||||
2+5^2=? // 27 (exponentiation before addition)
|
||||
(2+5)^2=? // 49
|
||||
2*3+4=? // 10
|
||||
2*(3+4)=? // 14
|
||||
~5=? // -6 (bitwise NOT - requires TE_BITWISE_OPERATORS)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 3. Number formats
|
||||
// ============================================================
|
||||
|
||||
42=? // 42
|
||||
3.14=? // 3.14
|
||||
.5=? // 0.5
|
||||
0x1F=? // 31 (hexadecimal)
|
||||
0xFF=? // 255
|
||||
0xFFFF=? // 65535
|
||||
0b101010=? // 42 (binary - NP3 extension)
|
||||
0b11111111=? // 255
|
||||
1e3=? // 1000 (scientific)
|
||||
2.5e-2=? // 0.025
|
||||
1.5e10=? // 15000000000
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 4. Basic math functions
|
||||
// ============================================================
|
||||
|
||||
ABS(-5)=? // 5
|
||||
ABS(7)=? // 7
|
||||
CEIL(2.3)=? // 3
|
||||
CEIL(-2.3)=? // -2
|
||||
FLOOR(2.7)=? // 2
|
||||
FLOOR(-2.7)=? // -3
|
||||
ROUND(3.456, 2)=? // 3.46
|
||||
ROUND(2.5, 0)=? // 3
|
||||
ROUND(-2.5, 0)=? // -3
|
||||
TRUNC(3.7)=? // 3
|
||||
TRUNC(-3.7)=? // -3
|
||||
SIGN(-7)=? // -1
|
||||
SIGN(7)=? // 1
|
||||
SIGN(0)=? // 0
|
||||
CLAMP(15, 0, 10)=? // 10
|
||||
CLAMP(-5, 0, 10)=? // 0
|
||||
CLAMP(5, 0, 10)=? // 5
|
||||
EVEN(3)=? // 4
|
||||
EVEN(-3)=? // -4
|
||||
ODD(4)=? // 5
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 5. Powers and roots
|
||||
// ============================================================
|
||||
|
||||
SQRT(16)=? // 4
|
||||
SQRT(2)=? // ~1.4142135623731
|
||||
SQRT(0)=? // 0
|
||||
POW(2, 10)=? // 1024
|
||||
POW(2, 0)=? // 1
|
||||
POW(0, 0)=? // 1
|
||||
POWER(3, 4)=? // 81
|
||||
EXP(0)=? // 1
|
||||
EXP(1)=? // ~2.71828182845905
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 6. Logarithms
|
||||
// ============================================================
|
||||
|
||||
LN(E)=? // 1
|
||||
LN(1)=? // 0
|
||||
LOG10(1000)=? // 3
|
||||
LOG10(1)=? // 0
|
||||
LOG10(100000)=? // 5
|
||||
LOG(100)=? // 2 (LOG = LOG10 for compatibility)
|
||||
log(1000)=? // 3 (lowercase 'log' - NP3 compat shim)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 7. Trigonometry (angles in radians)
|
||||
// ============================================================
|
||||
|
||||
SIN(0)=? // 0
|
||||
SIN(PI/2)=? // 1
|
||||
COS(0)=? // 1
|
||||
COS(PI)=? // -1
|
||||
TAN(0)=? // 0
|
||||
ASIN(1)=? // ~1.5707963267949 (= PI/2)
|
||||
ACOS(1)=? // 0
|
||||
ATAN(1)=? // ~0.7853981633974 (= PI/4)
|
||||
ATAN2(1, 1)=? // ~0.7853981633974 (= PI/4)
|
||||
ATAN2(1, 0)=? // ~1.5707963267949 (= PI/2)
|
||||
SINH(0)=? // 0
|
||||
COSH(0)=? // 1
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 8. Statistics (variadic - up to 24 args)
|
||||
// ============================================================
|
||||
|
||||
SUM(1, 2, 3)=? // 6
|
||||
SUM(1, 2, 3, 4, 5)=? // 15
|
||||
SUM(1.5, 2.5, 3)=? // 7
|
||||
AVERAGE(2, 4, 6)=? // 4
|
||||
AVERAGE(1, 2, 3, 4, 5)=? // 3
|
||||
MIN(3, 1, 2)=? // 1
|
||||
MIN(-5, -10, -2)=? // -10
|
||||
MAX(3, 1, 2)=? // 3
|
||||
MAX(-5, -10, -2)=? // -2
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 9. Combinatorics
|
||||
// ============================================================
|
||||
|
||||
FAC(0)=? // 1
|
||||
FAC(5)=? // 120
|
||||
FACT(6)=? // 720
|
||||
COMBIN(5, 2)=? // 10
|
||||
COMBIN(10, 3)=? // 120
|
||||
NCR(5, 2)=? // 10 (alias for COMBIN)
|
||||
PERMUT(5, 2)=? // 20
|
||||
PERMUT(10, 3)=? // 720
|
||||
NPR(5, 2)=? // 20 (alias for PERMUT)
|
||||
TGAMMA(5)=? // 24 (= 4!)
|
||||
GAMMA(6)=? // 120 (= 5!)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 10. Constants
|
||||
// ============================================================
|
||||
|
||||
PI=? // 3.14159265358979
|
||||
E=? // 2.71828182845905
|
||||
TRUE=? // 1
|
||||
FALSE=? // 0
|
||||
NAN=? // nan
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 11. NEW: Boolean detection - relational / equality operators
|
||||
// Result renders as 'true' / 'false' (lowercase).
|
||||
// ============================================================
|
||||
|
||||
1==1=? // true
|
||||
1==2=? // false
|
||||
1=1=? // true (lone '=' parses as '==')
|
||||
1=2=? // false (lone '=' parses as '==')
|
||||
1+1=2+2=? // false ((1+1) == (2+2) -> 2 == 4)
|
||||
1+1=2=? // true (parser-side, the surprising bit)
|
||||
1!=2=? // true
|
||||
1!=1=? // false
|
||||
1<>2=? // true (<> is alternative inequality)
|
||||
1<>1=? // false
|
||||
5<10=? // true
|
||||
5>10=? // false
|
||||
5<=5=? // true
|
||||
5>=5=? // true
|
||||
5<5=? // false
|
||||
5>5=? // false
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 12. NEW: Boolean detection - logical operators / functions
|
||||
// ============================================================
|
||||
|
||||
1 && 1=? // true
|
||||
1 && 0=? // false
|
||||
0 && 0=? // false
|
||||
1 || 0=? // true
|
||||
0 || 0=? // false
|
||||
!0=? // true
|
||||
!1=? // false
|
||||
AND(1, 1, 1)=? // true
|
||||
AND(1, 0, 1)=? // false
|
||||
OR(0, 0, 1)=? // true
|
||||
OR(0, 0, 0)=? // false
|
||||
NOT(0)=? // true
|
||||
NOT(1)=? // false
|
||||
TRUE && TRUE=? // true
|
||||
FALSE || TRUE=? // true
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 13. Boolean detection - the depth-0 rule
|
||||
// Operators inside parens DON'T count - except a single fully-
|
||||
// enclosing outer pair is stripped first.
|
||||
// ============================================================
|
||||
|
||||
(1==1)=? // true (outer parens stripped)
|
||||
((1==1))=? // true (recursive stripping)
|
||||
1+(1==1)=? // 2 (== is inside parens; result not 0/1)
|
||||
(1==1)*(2==2)=? // 1 (numeric; no depth-0 op)
|
||||
(1==1)+(0==1)=? // 1 (numeric; no depth-0 op)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 14. Conditionals - NOT detected as boolean
|
||||
// IF / IFS return arbitrary user-supplied branches; they are
|
||||
// intentionally NOT in the predicate list, so results render
|
||||
// as numeric values even when the condition is logical.
|
||||
// ============================================================
|
||||
|
||||
IF(1>0, 100, 200)=? // 100
|
||||
IF(1<0, 100, 200)=? // 200
|
||||
IF(1==1, 42, 99)=? // 42
|
||||
IF(AND(5>1, 5<10), 1, 0)=? // 1 (numeric; outer is IF, not AND)
|
||||
IFS(0, 1, 1, 2)=? // 2
|
||||
IFS(90>=90, 4, 90>=80, 3, 90>=70, 2, 1, 1)=? // 4
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 15. Error checking
|
||||
// ============================================================
|
||||
|
||||
NA()=? // nan
|
||||
ISERR(NAN)=? // true
|
||||
ISERR(5)=? // false
|
||||
ISERROR(0/0)=? // true
|
||||
ISNA(NAN)=? // true
|
||||
ISNAN(1.5)=? // false
|
||||
ISEVEN(4)=? // true
|
||||
ISEVEN(3)=? // false
|
||||
ISEVEN(0)=? // true
|
||||
ISODD(5)=? // true
|
||||
ISODD(4)=? // false
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 16. Bitwise functions
|
||||
// ============================================================
|
||||
|
||||
BITAND(0xF0, 0x3C)=? // 48 (= 0x30)
|
||||
BITAND(0b1100, 0b1010)=? // 8 (= 0b1000)
|
||||
BITOR(0xF0, 0x0F)=? // 255 (= 0xFF)
|
||||
BITOR(0b1100, 0b0011)=? // 15
|
||||
BITXOR(0xFF, 0xAA)=? // 85 (= 0x55)
|
||||
BITXOR(0b1010, 0b1010)=? // 0
|
||||
BITNOT(0)=? // 4294967295 (32-bit ~0 = 0xFFFFFFFF) - bit-width depends on build
|
||||
BITLSHIFT(1, 4)=? // 16
|
||||
BITLSHIFT(1, 20)=? // 1048576 (= 1 MiB)
|
||||
BITRSHIFT(256, 4)=? // 16
|
||||
BITRSHIFT(0xFF00, 8)=? // 255
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 17. Bit-shift / bit-rotate operators
|
||||
// ============================================================
|
||||
|
||||
1<<4=? // 16
|
||||
256>>4=? // 16
|
||||
0xFF<<8=? // 65280 (= 0xFF00)
|
||||
0xFF00>>8=? // 255 (= 0xFF)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 18. Precision and rounding (new %.15g format)
|
||||
// ============================================================
|
||||
|
||||
0.1+0.2=? // 0.3 (IEEE-754 surprise hidden by %.15g)
|
||||
1/3=? // 0.333333333333333
|
||||
2/3=? // 0.666666666666667
|
||||
100/2.54=? // 39.3700787401575 (cm to inches)
|
||||
2*PI=? // 6.28318530717959
|
||||
2*PI*6.371e6=? // 40030173.5921478 (Earth's circumference, meters)
|
||||
1.0000000001=? // 1.0000000001 (preserved: > 1e-15 cutoff)
|
||||
1.0000000000000001=? // 1 (clipped: <= 1e-15)
|
||||
2^53=? // 9007199254740992 (largest exact integer in double)
|
||||
2^60=? // 1152921504606846976 (still exact via %.21g)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 19. Non-finite results
|
||||
// ============================================================
|
||||
|
||||
0/0=? // nan
|
||||
1/0=? // inf
|
||||
-1/0=? // -inf
|
||||
SQRT(-1)=? // nan
|
||||
LN(0)=? // -inf
|
||||
LN(-1)=? // nan
|
||||
NAN+1=? // nan
|
||||
NAN==NAN=? // nan (comparison involving NaN -> NaN, NOT 'false')
|
||||
NAN==NAN || TRUE=? // true (short-circuit through TRUE keyword)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 20. Comments inside expressions
|
||||
// ============================================================
|
||||
|
||||
(3 + 4) /* this is a block comment */ * 2=? // 14
|
||||
5 + /* inline */ 3=? // 8
|
||||
2 /* multi
|
||||
line */ + 3=? // 5
|
||||
// Note: line-style '//' comments at the END of the line are tricky -
|
||||
// the '=?' trigger doesn't know about comments, so put '=?' BEFORE
|
||||
// any '//' annotation (which is the convention used throughout this file).
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 21. Compound real-world expressions
|
||||
// ============================================================
|
||||
|
||||
256*1024=? // 262144 (1 MiB in bytes)
|
||||
SQRT(3^2 + 4^2)=? // 5 (Pythagorean)
|
||||
(98.6 - 32) * 5/9=? // 37 (F -> C)
|
||||
72 * 0.0254=? // 1.8288 (inches -> meters)
|
||||
2^16 - 1=? // 65535 (uint16 max)
|
||||
2^32 - 1=? // 4294967295 (uint32 max)
|
||||
IF(5>3, MAX(1,2), MIN(3,4))=? // 2
|
||||
SUM(1,2,3) + AVERAGE(4,6)=? // 11 (= 6 + 5)
|
||||
ABS(SIN(PI))<1e-10=? // true (PI is approximate -> SIN(PI) is tiny non-zero)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 22. Hex / binary output modes
|
||||
//
|
||||
// These tests illustrate how the status-bar TinyExpr field changes
|
||||
// numeric formatting based on its mode (double-click the status
|
||||
// field to cycle: Decimal -> Hex -> Binary). Boolean results
|
||||
// OVERRIDE the mode and always show as 'true' / 'false'.
|
||||
// ============================================================
|
||||
|
||||
255=? // dec: 255 hex: 0xFF bin: 0b11111111
|
||||
4096=? // dec: 4096 hex: 0x1000 bin: 0b1000000000000
|
||||
-1=? // dec: -1 hex: 0xFFFFFFFF bin: 0b11111111111111111111111111111111
|
||||
1==1=? // true (mode ignored)
|
||||
1+1=2+2=? // false (mode ignored)
|
||||
|
||||
|
||||
// ============================================================
|
||||
// End of test file.
|
||||
// ============================================================
|
||||
Loading…
Reference in New Issue
Block a user