formula/inc/core_resource.hrc | 1 + formula/source/core/api/FormulaCompiler.cxx | 7 ++++++- formula/source/core/api/token.cxx | 9 ++++----- include/formula/compiler.hxx | 3 ++- include/formula/opcode.hxx | 2 ++ sc/inc/compiler.hxx | 2 +- sc/inc/global.hxx | 1 + sc/qa/unit/data/xls/user_defined_function.xls |binary sc/qa/unit/data/xlsx/user_defined_function.xlsx |binary sc/qa/unit/subsequent_export_test4.cxx | 17 +++++++++++++++++ sc/source/core/data/global.cxx | 6 ++++++ sc/source/core/opencl/formulagroupcl.cxx | 1 + sc/source/core/tool/compiler.cxx | 23 ++++++++++++++++++----- sc/source/core/tool/interpr4.cxx | 3 ++- sc/source/core/tool/parclass.cxx | 1 + sc/source/core/tool/token.cxx | 6 ++++-- sc/source/filter/excel/excform.cxx | 6 ++++-- sc/source/filter/excel/excform8.cxx | 8 ++++++-- sc/source/filter/excel/xeformula.cxx | 2 ++ sc/source/filter/excel/xicontent.cxx | 4 ++-- sc/source/filter/excel/xlformula.cxx | 4 +++- 21 files changed, 83 insertions(+), 23 deletions(-)
New commits: commit bce84f279755171addf29b61f4e8b09e89026c44 Author: Karthik Godha <[email protected]> AuthorDate: Wed Feb 18 18:21:54 2026 +0530 Commit: Balazs Varga <[email protected]> CommitDate: Tue Mar 10 15:01:00 2026 +0100 sc: Add new ocUDExternal token for user-defined functions Excel uses "_xludf." prefix for formulas containing unkown external functions. A new token ocUDExternal is created to handle unknown external functions bug-document: forum-mso-en4-352508.xls Change-Id: If08469e496ff76bb8b912211bd82c67363711d74 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199631 Reviewed-by: Balazs Varga <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> diff --git a/formula/inc/core_resource.hrc b/formula/inc/core_resource.hrc index cb2359afc069..b4038b68b07b 100644 --- a/formula/inc/core_resource.hrc +++ b/formula/inc/core_resource.hrc @@ -964,6 +964,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML[] = { "_xlfn.ORG.LIBREOFFICE.RANDBETWEEN.NV" , SC_OPCODE_RANDBETWEEN_NV }, { "_xlfn.RANDARRAY" , SC_OPCODE_RANDARRAY }, { "#REF!", SC_OPCODE_STOP }, + { "_xludf.", SC_OPCODE_UD_EXTERNAL }, { nullptr, -1 } }; diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx index 0b082ad5c86d..4cd3295075b8 100644 --- a/formula/source/core/api/FormulaCompiler.cxx +++ b/formula/source/core/api/FormulaCompiler.cxx @@ -1735,6 +1735,7 @@ void FormulaCompiler::Factor() // functions may have to be recalculated or not, we don't // know, classify as ONLOAD_LENIENT. case ocExternal: + case ocUDExternal: if (mpToken->GetExternal() == "com.sun.star.sheet.addin.Analysis.getRandbetween") pArr->SetExclusiveRecalcModeAlways(); else @@ -1885,6 +1886,7 @@ void FormulaCompiler::Factor() } else if ((SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR) || eOp == ocExternal + || eOp == ocUDExternal || eOp == ocMacro || eOp == ocAnd || eOp == ocOr @@ -2653,7 +2655,10 @@ const FormulaToken* FormulaCompiler::CreateStringFromToken( OUStringBuffer& rBuf // Don't export "#name!" in OOXML } else if( static_cast<sal_uInt16>(eOp) < mxSymbols->getSymbolCount()) // Keyword: - rBuffer.append( mxSymbols->getSymbol( eOp)); + { + if (eOp != ocUDExternal || maArrIterator.PeekNext()->GetOpCode() == ocOpen) + rBuffer.append(mxSymbols->getSymbol(eOp)); + } else { SAL_WARN( "formula.core","unknown OpCode"); diff --git a/formula/source/core/api/token.cxx b/formula/source/core/api/token.cxx index 51521a716183..71b85606ab50 100644 --- a/formula/source/core/api/token.cxx +++ b/formula/source/core/api/token.cxx @@ -79,7 +79,7 @@ bool FormulaToken::IsFunction() const || (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR) // one parameter || (SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR) // x parameters (cByte==0 in // FuncAutoPilot) - || eOp == ocMacro || eOp == ocExternal // macros, AddIns + || eOp == ocMacro || eOp == ocExternal || eOp == ocUDExternal // macros, AddIns || eOp == ocAnd || eOp == ocOr // former binary, now x parameters || (eOp >= ocInternalBegin && eOp <= ocInternalEnd) // internal )); @@ -88,9 +88,8 @@ bool FormulaToken::IsFunction() const sal_uInt8 FormulaToken::GetParamCount() const { - if ( eOp < SC_OPCODE_STOP_DIV && eOp != ocExternal && eOp != ocMacro && - !FormulaCompiler::IsOpCodeJumpCommand( eOp ) && - eOp != ocPercentSign ) + if (eOp < SC_OPCODE_STOP_DIV && eOp != ocExternal && eOp != ocMacro && eOp != ocUDExternal + && !FormulaCompiler::IsOpCodeJumpCommand(eOp) && eOp != ocPercentSign) return 0; // parameters and specials // ocIf... jump commands not for FAP, have cByte then //2do: bool parameter whether FAP or not? @@ -435,7 +434,7 @@ bool FormulaTokenArray::AddFormulaToken( AddStringXML( aStrVal ); else if ( eOpCode == ocStringName ) AddStringName( aStrVal ); - else if ( eOpCode == ocExternal || eOpCode == ocMacro ) + else if ( eOpCode == ocExternal || eOpCode == ocMacro || eOpCode == ocUDExternal) Add( new formula::FormulaExternalToken( eOpCode, aStrVal ) ); else if ( eOpCode == ocWhitespace ) { diff --git a/include/formula/compiler.hxx b/include/formula/compiler.hxx index 64f6c1fad97a..6d8f34a32171 100644 --- a/include/formula/compiler.hxx +++ b/include/formula/compiler.hxx @@ -532,7 +532,8 @@ #define SC_OPCODE_UNIQUE 517 #define SC_OPCODE_WRAPCOLS 518 #define SC_OPCODE_WRAPROWS 519 -#define SC_OPCODE_STOP_2_PAR 520 /* last function with two or more parameters' OpCode + 1 */ +#define SC_OPCODE_UD_EXTERNAL 520 /* User-defined external function */ +#define SC_OPCODE_STOP_2_PAR 521 /* last function with two or more parameters' OpCode + 1 */ #define SC_OPCODE_STOP_FUNCTION SC_OPCODE_STOP_2_PAR /* last function's OpCode + 1 */ #define SC_OPCODE_LAST_OPCODE_ID (SC_OPCODE_STOP_FUNCTION - 1) /* last OpCode */ diff --git a/include/formula/opcode.hxx b/include/formula/opcode.hxx index 5119b024f0d0..287887c10769 100644 --- a/include/formula/opcode.hxx +++ b/include/formula/opcode.hxx @@ -32,6 +32,7 @@ enum OpCode : sal_uInt16 ocCall = SC_OPCODE_CALL, ocStop = SC_OPCODE_STOP, ocExternal = SC_OPCODE_EXTERNAL, + ocUDExternal = SC_OPCODE_UD_EXTERNAL, ocName = SC_OPCODE_NAME, // Jump commands ocIf = SC_OPCODE_IF, @@ -552,6 +553,7 @@ inline std::string OpCodeEnumToString(OpCode eCode) case ocCall: return "Call"; case ocStop: return "Stop"; case ocExternal: return "External"; + case ocUDExternal: return "UDExternal"; case ocName: return "Name"; case ocIf: return "If"; case ocIfError: return "IfError"; diff --git a/sc/inc/compiler.hxx b/sc/inc/compiler.hxx index 95ac76286f0f..01351c40a009 100644 --- a/sc/inc/compiler.hxx +++ b/sc/inc/compiler.hxx @@ -166,7 +166,7 @@ public: void SetExternalSingleRef( sal_uInt16 nFileId, const OUString& rTabName, const ScSingleRefData& rRef ); void SetExternalDoubleRef( sal_uInt16 nFileId, const OUString& rTabName, const ScComplexRefData& rRef ); void SetExternalName( sal_uInt16 nFileId, const OUString& rName ); - void SetExternal(const OUString& rStr); + void SetExternal(const OUString& rStr, OpCode eCode = ocExternal); /** If the token is a non-external reference, determine if the reference is valid. If the token is an external reference, return true. Else return diff --git a/sc/inc/global.hxx b/sc/inc/global.hxx index 4692dc791220..154e8e1f9ba8 100644 --- a/sc/inc/global.hxx +++ b/sc/inc/global.hxx @@ -578,6 +578,7 @@ public: static void ClearAutoFormat(); //BugId 54209 static LegacyFuncCollection* GetLegacyFuncCollection(); SC_DLLPUBLIC static ScUnoAddInCollection* GetAddInCollection(); + SC_DLLPUBLIC static bool IsValidExternal(const OUString& rAddIn); SC_DLLPUBLIC static ScUserList& GetUserList(); static void SetUserList( const ScUserList* pNewList ); /** diff --git a/sc/qa/unit/data/xls/user_defined_function.xls b/sc/qa/unit/data/xls/user_defined_function.xls new file mode 100644 index 000000000000..d272c1db1223 Binary files /dev/null and b/sc/qa/unit/data/xls/user_defined_function.xls differ diff --git a/sc/qa/unit/data/xlsx/user_defined_function.xlsx b/sc/qa/unit/data/xlsx/user_defined_function.xlsx new file mode 100644 index 000000000000..171e4e884333 Binary files /dev/null and b/sc/qa/unit/data/xlsx/user_defined_function.xlsx differ diff --git a/sc/qa/unit/subsequent_export_test4.cxx b/sc/qa/unit/subsequent_export_test4.cxx index 98749cd9a945..5f6ba6b30c3b 100644 --- a/sc/qa/unit/subsequent_export_test4.cxx +++ b/sc/qa/unit/subsequent_export_test4.cxx @@ -2487,6 +2487,23 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testMacrosInXLSX) assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[2]/x:c[2]/x:f", u""); } +CPPUNIT_TEST_FIXTURE(ScExportTest4, testUserDefinedFunctions) +{ + createScDoc("xls/user_defined_function.xls"); + save(u"Calc Office Open XML"_ustr); + xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr); + CPPUNIT_ASSERT(pSheet); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[44]/x:c[1]/x:f", + u"_xludf.Sum(B9:C42)"); + + createScDoc("xlsx/user_defined_function.xlsx"); + save(u"Calc Office Open XML"_ustr); + pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr); + CPPUNIT_ASSERT(pSheet); + assertXPathContent(pSheet, "/x:worksheet/x:sheetData/x:row[42]/x:c[1]/x:f", + u"_xludf.SUM(B9:C42)"); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/data/global.cxx b/sc/source/core/data/global.cxx index 56f19267f47f..6cfbfee4e12d 100644 --- a/sc/source/core/data/global.cxx +++ b/sc/source/core/data/global.cxx @@ -291,6 +291,12 @@ ScUnoAddInCollection* ScGlobal::GetAddInCollection() return comphelper::doubleCheckedInit( pAddInCollection, []() { return new ScUnoAddInCollection(); }); } +bool ScGlobal::IsValidExternal(const OUString& rAddIn) +{ + return (ScGlobal::GetLegacyFuncCollection()->findByName(rAddIn) + || !ScGlobal::GetAddInCollection()->FindFunction(rAddIn, false).isEmpty()); +} + ScUserList& ScGlobal::GetUserList() { assert(!bThreadedGroupCalcInProgress); diff --git a/sc/source/core/opencl/formulagroupcl.cxx b/sc/source/core/opencl/formulagroupcl.cxx index 1a88b55c961c..aaba63d7b8e8 100644 --- a/sc/source/core/opencl/formulagroupcl.cxx +++ b/sc/source/core/opencl/formulagroupcl.cxx @@ -2369,6 +2369,7 @@ DynamicKernelSoPArguments::DynamicKernelSoPArguments(const ScCalcConfig& config, CASE(ocZTest, std::make_shared<OpZTest>()) #undef CASE case ocExternal: + case ocUDExternal: #define EXTCASE( name, createCode ) \ else if (pChild->GetExternal() == name) \ { \ diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx index 3ef89dad82d8..6a45f6e70076 100644 --- a/sc/source/core/tool/compiler.cxx +++ b/sc/source/core/tool/compiler.cxx @@ -3131,12 +3131,14 @@ bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray ) if (!bFound) { - OUString aIntName; + OUString aIntName, aFuncName = rName, sUDPrefix = mxSymbols->getSymbol(ocUDExternal); + if (aFuncName.matchIgnoreAsciiCase(sUDPrefix)) + aFuncName = aFuncName.copy(sUDPrefix.getLength()); + if (mxSymbols->hasExternals()) { // If symbols are set by filters get mapping to exact name. - ExternalHashMap::const_iterator iExt( - mxSymbols->getExternalHashMap().find( rName)); + ExternalHashMap::const_iterator iExt(mxSymbols->getExternalHashMap().find(aFuncName)); if (iExt != mxSymbols->getExternalHashMap().end()) { if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second)) @@ -3153,14 +3155,20 @@ bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray ) else // bLocalFirst=false for (English) upper full original name // (service.function) - aIntName = ScGlobal::GetAddInCollection()->FindFunction( - rName, !mxSymbols->isEnglish()); + aIntName = ScGlobal::GetAddInCollection()->FindFunction(aFuncName, + !mxSymbols->isEnglish()); } if (!aIntName.isEmpty()) { maRawToken.SetExternal( aIntName ); // international name bFound = true; } + else if (rName != aFuncName) + { + // User-defined function + maRawToken.SetExternal(aFuncName, ocUDExternal); + bFound = true; + } } if (!bFound) return false; @@ -3789,6 +3797,11 @@ bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInval if (!pConv->parseExternalName( rSymbol, aFile, aName, rDoc, &maExternalLinks)) return false; + // Remove the user-defined flag + OUString sUDPrefix = mxSymbols->getSymbol(ocUDExternal); + if (aName.matchIgnoreAsciiCase(sUDPrefix)) + aName = aName.copy(sUDPrefix.getLength()); + if (aFile.getLength() > MAXSTRLEN || aName.getLength() > MAXSTRLEN) return false; diff --git a/sc/source/core/tool/interpr4.cxx b/sc/source/core/tool/interpr4.cxx index 71e53e77a3f2..e5c46726b822 100644 --- a/sc/source/core/tool/interpr4.cxx +++ b/sc/source/core/tool/interpr4.cxx @@ -4538,7 +4538,8 @@ StackVar ScInterpreter::Interpret() case ocBetaInv : case ocBetaInv_MS : ScBetaInv(); break; case ocFourier : ScFourier(); break; - case ocExternal : ScExternal(); break; + case ocExternal : + case ocUDExternal : ScExternal(); break; case ocTableOp : ScTableOp(); break; case ocStop : break; case ocErrorType : ScErrorType(); break; diff --git a/sc/source/core/tool/parclass.cxx b/sc/source/core/tool/parclass.cxx index e7e07cdccdd2..4ecc1f64d807 100644 --- a/sc/source/core/tool/parclass.cxx +++ b/sc/source/core/tool/parclass.cxx @@ -390,6 +390,7 @@ formula::ParamClass ScParameterClassification::GetParameterType( switch ( eOp ) { case ocExternal: + case ocUDExternal: return GetExternalParameterType( pToken, nParameter); case ocMacro: return (nParameter == SAL_MAX_UINT16 ? Value : Reference); diff --git a/sc/source/core/tool/token.cxx b/sc/source/core/tool/token.cxx index 66e770943e30..32ba0c3a038b 100644 --- a/sc/source/core/tool/token.cxx +++ b/sc/source/core/tool/token.cxx @@ -338,9 +338,9 @@ void ScRawToken::SetExternalName( sal_uInt16 nFileId, const OUString& rName ) maExternalName = rName; } -void ScRawToken::SetExternal( const OUString& rStr ) +void ScRawToken::SetExternal( const OUString& rStr, OpCode eCode ) { - eOp = ocExternal; + eOp = eCode; eType = svExternal; maExternalName = rStr; } @@ -1342,6 +1342,7 @@ void ScTokenArray::CheckForThreading( const FormulaToken& r ) ocText, ocSheet, ocExternal, + ocUDExternal, ocDde, ocWebservice, ocGetPivotData @@ -1706,6 +1707,7 @@ void ScTokenArray::CheckToken( const FormulaToken& r ) // Known good, don't change state. case ocStop: case ocExternal: + case ocUDExternal: case ocOpen: case ocClose: case ocSep: diff --git a/sc/source/filter/excel/excform.cxx b/sc/source/filter/excel/excform.cxx index fb280752ec85..f5e0bb607ea3 100644 --- a/sc/source/filter/excel/excform.cxx +++ b/sc/source/filter/excel/excform.cxx @@ -532,8 +532,10 @@ ConvErr ExcelToSc::Convert( std::unique_ptr<ScTokenArray>& pResult, XclImpStream aStack << aPool.Store(ocMacro, pName->GetXclName()); else if (pName->GetScRangeData()) aStack << aPool.StoreName(nUINT16, -1); - else + else if (ScGlobal::IsValidExternal(pName->GetXclName())) aStack << aPool.Store(ocExternal, pName->GetXclName()); + else + aStack << aPool.Store(ocUDExternal, pName->GetXclName()); } } break; @@ -1542,7 +1544,7 @@ void ExcelToSc::DoMulArgs( DefTokenId eId, sal_uInt8 nCnt ) if( nPass < nCnt ) nCnt = static_cast< sal_uInt8 >( nPass ); - if( nCnt > 0 && eId == ocExternal ) + if( nCnt > 0 && (eId == ocExternal || eId == ocUDExternal) ) { TokenId n = eParam[ nCnt - 1 ]; //##### ADJUST STUPIDITY FOR BASIC-FUNCS! diff --git a/sc/source/filter/excel/excform8.cxx b/sc/source/filter/excel/excform8.cxx index 68eaf5710a23..ea79d17e42a2 100644 --- a/sc/source/filter/excel/excform8.cxx +++ b/sc/source/filter/excel/excform8.cxx @@ -486,8 +486,10 @@ ConvErr ExcelToSc8::Convert( std::unique_ptr<ScTokenArray>& rpTokArray, XclImpSt || pName->HasTokens()) // check forward declaration aStack << aPool.StoreName(nUINT16, pName->IsGlobal() ? -1 : pName->GetScTab()); - else + else if (ScGlobal::IsValidExternal(pName->GetXclName())) aStack << aPool.Store(ocExternal, pName->GetXclName()); + else + aStack << aPool.Store(ocUDExternal, pName->GetXclName()); } break; } @@ -671,8 +673,10 @@ ConvErr ExcelToSc8::Convert( std::unique_ptr<ScTokenArray>& rpTokArray, XclImpSt aStack << aPool.StoreName( nNameIdx, pName->IsGlobal() ? -1 : pName->GetScTab()); else if (pName->IsMacro()) aStack << aPool.Store(ocMacro, pName->GetXclName()); - else + else if (ScGlobal::IsValidExternal(pName->GetXclName())) aStack << aPool.Store(ocExternal, pName->GetXclName()); + else + aStack << aPool.Store(ocUDExternal, pName->GetXclName()); } } else if( const XclImpExtName* pExtName = rLinkMan.GetExternName( nXtiIndex, nNameIdx ) ) diff --git a/sc/source/filter/excel/xeformula.cxx b/sc/source/filter/excel/xeformula.cxx index 91b7ec58fa89..4a0661728dc7 100644 --- a/sc/source/filter/excel/xeformula.cxx +++ b/sc/source/filter/excel/xeformula.cxx @@ -1686,6 +1686,7 @@ void XclExpFmlaCompImpl::AppendDefaultParam( XclExpFuncData& rFuncData ) switch( rFuncData.GetOpCode() ) { case ocExternal: + case ocUDExternal: AppendAddInCallToken( rFuncData.GetExtFuncData() ); break; case ocEuroConvert: @@ -1763,6 +1764,7 @@ void XclExpFmlaCompImpl::AppendTrailingParam( XclExpFuncData& rFuncData ) break; case ocExternal: + case ocUDExternal: case ocMacro: // external or macro call without parameters needs the external name reference if( nParamCount == 0 ) diff --git a/sc/source/filter/excel/xicontent.cxx b/sc/source/filter/excel/xicontent.cxx index a121cb6d4f61..bbf9e9127bb4 100644 --- a/sc/source/filter/excel/xicontent.cxx +++ b/sc/source/filter/excel/xicontent.cxx @@ -666,7 +666,7 @@ void XclImpCondFormat::ReadCF( XclImpStream& rStrm ) // If it's an external formula then skip the entry if (const formula::FormulaToken* pToken = pTokArr->FirstToken()) { - if (pToken->GetOpCode() == ocExternal) + if (pToken->GetOpCode() == ocExternal || pToken->GetOpCode() == ocUDExternal) return; } xTokArr1 = std::move( pTokArr ); @@ -686,7 +686,7 @@ void XclImpCondFormat::ReadCF( XclImpStream& rStrm ) // If it's an external formula then skip the entry if (const formula::FormulaToken* pToken = pTokArr->FirstToken()) { - if (pToken->GetOpCode() == ocExternal) + if (pToken->GetOpCode() == ocExternal || pToken->GetOpCode() == ocUDExternal) return; } xTokArr2 = std::move( pTokArr ); diff --git a/sc/source/filter/excel/xlformula.cxx b/sc/source/filter/excel/xlformula.cxx index 2f8b2756d6c9..6d62d8ae8923 100644 --- a/sc/source/filter/excel/xlformula.cxx +++ b/sc/source/filter/excel/xlformula.cxx @@ -219,7 +219,8 @@ const XclFunctionInfo saFuncTable_2[] = { ocLenB, 211, 1, 1, V, { VR }, 0, nullptr }, { ocRoundUp, 212, 2, 2, V, { VR }, 0, nullptr }, { ocRoundDown, 213, 2, 2, V, { VR }, 0, nullptr }, - { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr } + { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, + { ocUDExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr } }; /** Functions new in BIFF3. */ @@ -348,6 +349,7 @@ const XclFunctionInfo saFuncTable_5[] = { ocGetDiffDate360, 220, 2, 3, V, { VR }, 0, nullptr }, // BIFF3-4: 2, BIFF5: 2-3 { ocMacro, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocUDExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, { ocConcat, 336, 0, MX, V, { VR }, 0, nullptr }, { ocPower, 337, 2, 2, V, { VR }, 0, nullptr }, { ocRad, 342, 1, 1, V, { VR }, 0, nullptr },
