sc: add @ prefix operator for opting out of array spill

The @ operator collapses an array operand to its upper-left value.
It is the implicit-intersection prefix used to opt a formula out
of the dynamic-array spill behaviour. =@SEQUENCE(4) returns 1
instead of the four-row spill.

The new ocSingleValue opcode joins the unary-operator range so the
existing compile and interpret machinery treats it like any other
unary prefix. The @ character stops being part of name syntax in
the OOXML grammar, so sheet names that contain @ have to be quoted
from now on.

In-session only for now. The operator is parsed and interpreted
but not yet serialised into XLSX or ODS.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I18cf56e34f933ebb7f6e43c157618c9fe28dbe0f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4172
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit fbd04d7c394ad7445a667bf63820f88551b176e1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206673
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Tomaž Vajngerl
2026-06-16 09:14:42 +02:00
committed by Xisco Fauli
parent 9537ccc6fe
commit 6f1fb25a94
7 changed files with 56 additions and 3 deletions
+6
View File
@@ -68,6 +68,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF[] =
{ "NOT" , ocNot },
{ "NEG" , ocNeg },
{ "-" , ocNegSub },
{ "@" , ocSingleValue },
{ "PI" , ocPi },
{ "RAND" , ocRandom },
{ "TRUE" , ocTrue },
@@ -540,6 +541,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML[] =
{ "NOT" , ocNot },
{ "NEG" , ocNeg },
{ "-" , ocNegSub },
{ "@" , ocSingleValue },
{ "PI" , ocPi },
{ "RAND" , ocRandom },
{ "TRUE" , ocTrue },
@@ -1017,6 +1019,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF[] =
{ "NOT" , ocNot },
{ "NEG" , ocNeg },
{ "-" , ocNegSub },
{ "@" , ocSingleValue },
{ "PI" , ocPi },
{ "RAND" , ocRandom },
{ "TRUE" , ocTrue },
@@ -1492,6 +1495,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_API[] =
{ "NOT" , ocNot },
{ "NEG" , ocNeg },
{ "-" , ocNegSub },
{ "@" , ocSingleValue },
{ "PI" , ocPi },
{ "RAND" , ocRandom },
{ "TRUE" , ocTrue },
@@ -1965,6 +1969,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH[] =
{ "NOT" , ocNot },
{ "NEG" , ocNeg },
{ "-" , ocNegSub },
{ "@" , ocSingleValue },
{ "PI" , ocPi },
{ "RAND" , ocRandom },
{ "TRUE" , ocTrue },
@@ -2892,6 +2897,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_SYMBOLS[] =
{ "~" , ocUnion },
{ ":" , ocRange },
{ "-" , ocNegSub },
{ "@" , ocSingleValue },
{ nullptr, -1 }
};
+4 -1
View File
@@ -113,7 +113,9 @@ enum OpCode : sal_uInt16
// Unary operators
ocStartUnaryOperators = 70,
ocNegSub = 70,
ocStopUnaryOperators = 71,
// The @ single-value prefix operator (implicit intersection).
ocSingleValue = 71,
ocStopUnaryOperators = 72,
ocStartFunction = 75,
@@ -646,6 +648,7 @@ inline std::string OpCodeEnumToString(OpCode eCode)
case ocNot: return "Not";
case ocNeg: return "Neg";
case ocNegSub: return "NegSub";
case ocSingleValue: return "SingleValue";
case ocPi: return "Pi";
case ocRandom: return "Random";
case ocRandomNV: return "RandomNV";
+17
View File
@@ -5767,6 +5767,23 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testDynamicArrayFlagCopy)
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(TestFormula2, testSingleValueOperator)
{
// The @ prefix collapses an array-returning function to its upper-left
// value, opting the formula out of auto-spill.
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
m_pDoc->SetFormula(ScAddress(0, 0, 0), u"=@SEQUENCE(4)"_ustr,
formula::FormulaGrammar::GRAM_NATIVE);
CPPUNIT_ASSERT_EQUAL(1.0, m_pDoc->GetValue(ScAddress(0, 0, 0)));
// No spill into the row below.
CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(0, 1, 0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(TestFormula2, testDynamicArrayResizeDuringCopyToClip)
{
// A dynamic-array resize queued during CopyToClip must run after the clipboard clone is made.
+1
View File
@@ -610,6 +610,7 @@ private:
void ScXor();
void ScNot();
void ScNeg();
void ScSingleValue();
void ScPercentSign();
void ScIntersect();
void ScRangeFunc();
+4 -2
View File
@@ -141,7 +141,8 @@ constexpr std::array<ScCharFlags, 128> makeCommonCharTable()
a['='] = ScCharFlags::Char | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
a['>'] = ScCharFlags::CharBool | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
a['?'] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::Name;
/* @ */ // FREE
// @ is the prefix operator that opts a formula out of auto-spill.
a['@'] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
for (int i = 'A'; i <= 'Z'; i++)
a[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
@@ -211,7 +212,8 @@ constexpr std::array<ScCharFlags, 128> makeCharTable_XL()
a['='] |= ScCharFlags::Word;
a['>'] |= ScCharFlags::Word;
/* ? */ // question really is not permitted in sheet name
a['@'] |= ScCharFlags::Word;
// @ stays an operator in the XL grammar too. Sheet names that contain
// @ have to be quoted.
a['['] = ScCharFlags::Word;
a[']'] = ScCharFlags::Word;
a['{'] |= ScCharFlags::Word;
+23
View File
@@ -1543,6 +1543,29 @@ void ScInterpreter::ScNeg()
}
}
void ScInterpreter::ScSingleValue()
{
// The @ operator collapses an array operand to a single value. It is
// the implicit-intersection prefix that opts a formula out of the
// dynamic-array spill behaviour.
nFuncFmtType = nCurFmtType;
if (GetStackType() == svMatrix)
{
ScMatrixRef pMat = GetMatrix();
if (!pMat)
{
PushIllegalParameter();
return;
}
ScMatrixValue aVal = pMat->Get(0, 0);
if (aVal.nType == ScMatValType::String || aVal.nType == ScMatValType::Empty)
PushString(aVal.aStr);
else
PushDouble(aVal.fVal);
}
// Non-matrix operand passes through unchanged.
}
void ScInterpreter::ScPercentSign()
{
nFuncFmtType = SvNumFormatType::PERCENT;
+1
View File
@@ -4154,6 +4154,7 @@ StackVar ScInterpreter::Interpret()
case ocNot : ScNot(); break;
case ocNegSub :
case ocNeg : ScNeg(); break;
case ocSingleValue : ScSingleValue(); break;
case ocPercentSign : ScPercentSign(); break;
case ocPi : ScPi(); break;
case ocRandom : ScRandom(); break;