Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/include/formula/token.hxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#pragma once
21
22
#include <sal/config.h>
23
24
#include <cstring>
25
#include <memory>
26
#include <utility>
27
#include <vector>
28
29
#include <formula/formuladllapi.h>
30
#include <formula/opcode.hxx>
31
#include <formula/types.hxx>
32
#include <formula/paramclass.hxx>
33
#include <formula/errorcodes.hxx>
34
#include <formula/callable.hxx>
35
#include <osl/interlck.h>
36
#include <rtl/ustring.hxx>
37
#include <sal/types.h>
38
#include <svl/sharedstring.hxx>
39
40
class ScJumpMatrix;
41
class ScMatrix;
42
struct ScComplexRefData;
43
struct ScSingleRefData;
44
45
namespace formula
46
{
47
48
enum StackVar : sal_uInt8
49
{
50
    svByte,
51
    svDouble,
52
    svString,
53
    svStringName,
54
    svCallable,
55
    svDPFieldName,
56
    svSingleRef,
57
    svDoubleRef,
58
    svMatrix,
59
    svIndex,
60
    svJump,
61
    svExternal,                         // Byte + String
62
    svFAP,                              // FormulaAutoPilot only, ever exported
63
    svJumpMatrix,
64
    svRefList,                          // ocUnion result
65
    svEmptyCell,                        // Result is an empty cell, e.g. in LOOKUP()
66
67
    svMatrixCell,                       // Result is a matrix with bells and
68
                                        // whistles as needed for _the_ matrix
69
                                        // formula result.
70
71
    svHybridCell,                       // A temporary condition of a formula
72
                                        // cell during import, having a double
73
                                        // and/or string result and a formula
74
                                        // string to be compiled.
75
76
    svExternalSingleRef,
77
    svExternalDoubleRef,
78
    svExternalName,
79
    svSingleVectorRef,
80
    svDoubleVectorRef,
81
    svError,                            // error token
82
    svMissing,                          // 0 or ""
83
    svSep,                              // separator, ocSep, ocOpen, ocClose
84
    svUnknown                           // unknown StackType
85
};
86
87
// Only to be used for debugging output. No guarantee of stability of the
88
// return value.
89
90
// Turn this into an operator<< when StackVar becomes a scoped enum
91
92
inline std::string StackVarEnumToString(StackVar const e)
93
0
{
94
0
    switch (e)
95
0
    {
96
0
        case svByte:              return "Byte";
97
0
        case svDouble:            return "Double";
98
0
        case svString:            return "String";
99
0
        case svStringName:        return "StringName";
100
0
        case svCallable:          return "Callable";
101
0
        case svDPFieldName:       return "DPFieldName";
102
0
        case svSingleRef:         return "SingleRef";
103
0
        case svDoubleRef:         return "DoubleRef";
104
0
        case svMatrix:            return "Matrix";
105
0
        case svIndex:             return "Index";
106
0
        case svJump:              return "Jump";
107
0
        case svExternal:          return "External";
108
0
        case svFAP:               return "FAP";
109
0
        case svJumpMatrix:        return "JumpMatrix";
110
0
        case svRefList:           return "RefList";
111
0
        case svEmptyCell:         return "EmptyCell";
112
0
        case svMatrixCell:        return "MatrixCell";
113
0
        case svHybridCell:        return "HybridCell";
114
0
        case svExternalSingleRef: return "ExternalSingleRef";
115
0
        case svExternalDoubleRef: return "ExternalDoubleRef";
116
0
        case svExternalName:      return "ExternalName";
117
0
        case svSingleVectorRef:   return "SingleVectorRef";
118
0
        case svDoubleVectorRef:   return "DoubleVectorRef";
119
0
        case svError:             return "Error";
120
0
        case svMissing:           return "Missing";
121
0
        case svSep:               return "Sep";
122
0
        case svUnknown:           return "Unknown";
123
0
    }
124
0
    std::ostringstream os;
125
0
    os << static_cast<int>(e);
126
0
    return os.str();
127
0
}
128
129
enum class RefCntPolicy : sal_uInt8
130
{
131
    ThreadSafe, // refcounting via thread-safe oslInterlockedCount
132
    UnsafeRef,  // refcounting done with no locking/guarding against concurrent access
133
    None        // no ref counting done
134
};
135
136
class FORMULA_DLLPUBLIC FormulaToken
137
{
138
    OpCode                      eOp;
139
    const StackVar              eType;           // type of data
140
    RefCntPolicy                eRefCntPolicy;   // style of reference counting
141
    mutable oslInterlockedCount mnRefCnt;        // reference count
142
143
    FormulaToken&            operator=( const FormulaToken& ) = delete;
144
public:
145
    FormulaToken( StackVar eTypeP,OpCode e = ocPush );
146
    FormulaToken( const FormulaToken& r );
147
148
    virtual                     ~FormulaToken();
149
150
197M
    void                Delete()                { delete this; }
151
466k
    void                DeleteIfZeroRef()       { if (mnRefCnt == 0) delete this; }
152
1.64G
    StackVar            GetType() const         { return eType; }
153
    bool                IsFunction() const; // pure functions, no operators
154
155
    bool IsExternalRef() const;
156
    bool IsRef() const;
157
158
            sal_uInt8           GetParamCount() const;
159
160
    void IncRef() const
161
421M
    {
162
421M
        switch (eRefCntPolicy)
163
421M
        {
164
419M
            case RefCntPolicy::ThreadSafe:
165
419M
            default:
166
419M
                osl_atomic_increment(&mnRefCnt);
167
419M
                break;
168
1.70M
            case RefCntPolicy::UnsafeRef:
169
1.70M
                ++mnRefCnt;
170
1.70M
                break;
171
0
            case RefCntPolicy::None:
172
0
                break;
173
421M
        }
174
421M
    }
175
176
    void DecRef() const
177
421M
    {
178
421M
        switch (eRefCntPolicy)
179
421M
        {
180
419M
            case RefCntPolicy::ThreadSafe:
181
419M
            default:
182
419M
                if (!osl_atomic_decrement(&mnRefCnt))
183
197M
                    const_cast<FormulaToken*>(this)->Delete();
184
419M
                break;
185
1.70M
            case RefCntPolicy::UnsafeRef:
186
1.70M
                if (!--mnRefCnt)
187
409k
                    const_cast<FormulaToken*>(this)->Delete();
188
1.70M
                break;
189
0
            case RefCntPolicy::None:
190
0
                break;
191
421M
        }
192
421M
    }
193
194
409k
    void SetRefCntPolicy(RefCntPolicy ePolicy) { eRefCntPolicy = ePolicy; }
195
469k
    RefCntPolicy GetRefCntPolicy() const { return eRefCntPolicy; }
196
197
247M
    oslInterlockedCount GetRef() const       { return mnRefCnt; }
198
1.42G
    OpCode              GetOpCode() const    { return eOp; }
199
200
    bool                IsInForceArray() const;
201
202
    /**
203
        Dummy methods to avoid switches and casts where possible,
204
        the real token classes have to override the appropriate method[s].
205
        The only methods valid anytime if not overridden are:
206
207
        - GetByte() since this represents the count of parameters to a function
208
          which of course is 0 on non-functions. FormulaByteToken and ScExternal do
209
          override it.
210
211
        - GetInForceArray() since also this is only used for operators and
212
          functions and is ParamClass::Unknown for other tokens.
213
214
        Any other non-overridden method pops up an assertion.
215
     */
216
217
    virtual ParamClass          GetInForceArray() const;
218
    virtual void                SetInForceArray( ParamClass c );
219
220
84.6M
    virtual FormulaToken*       Clone() const { return new FormulaToken(*this); }
221
222
    virtual bool                TextEqual( const formula::FormulaToken& rToken ) const;
223
    virtual bool                operator==( const FormulaToken& rToken ) const;
224
225
    /** This is dirty and only the compiler should use it! */
226
7.32k
    struct PrivateAccess { friend class FormulaCompiler; private: PrivateAccess() { }  };
227
7.32k
    void                NewOpCode( OpCode e, const PrivateAccess&  ) { eOp = e; }
228
};
229
230
inline void intrusive_ptr_add_ref(const FormulaToken* p)
231
121M
{
232
121M
    p->IncRef();
233
121M
}
234
235
inline void intrusive_ptr_release(const FormulaToken* p)
236
121M
{
237
121M
    p->DecRef();
238
121M
}
239
240
class FORMULA_DLLPUBLIC FormulaSpaceToken final : public FormulaToken
241
{
242
private:
243
            sal_uInt8           nByte;
244
            sal_Unicode         cChar;
245
public:
246
                                FormulaSpaceToken( sal_uInt8 n, sal_Unicode c ) :
247
50.0k
                                    FormulaToken( svByte, ocWhitespace ),
248
50.0k
                                    nByte( n ), cChar( c ) {}
249
                                FormulaSpaceToken( const FormulaSpaceToken& r ) :
250
2.28k
                                    FormulaToken( r ),
251
2.28k
                                    nByte( r.nByte ), cChar( r.cChar ) {}
252
253
2.28k
    virtual FormulaToken*       Clone() const override { return new FormulaSpaceToken(*this); }
254
    sal_uInt8                   GetByte() const;
255
    sal_Unicode                 GetChar() const;
256
    virtual bool                operator==( const FormulaToken& rToken ) const override;
257
};
258
259
class FORMULA_DLLPUBLIC FormulaByteToken final : public FormulaToken
260
{
261
private:
262
            sal_uInt8           mnByte;
263
            ParamClass          eInForceArray;
264
public:
265
                                FormulaByteToken( OpCode e, sal_uInt8 n, ParamClass c ) :
266
5.47M
                                    FormulaToken( svByte,e ), mnByte( n ),
267
5.47M
                                    eInForceArray( c ) {}
268
                                FormulaByteToken( OpCode e, sal_uInt8 n ) :
269
199k
                                    FormulaToken( svByte,e ), mnByte( n ),
270
199k
                                    eInForceArray( ParamClass::Unknown ) {}
271
                                FormulaByteToken( OpCode e ) :
272
1.74M
                                    FormulaToken( svByte,e ), mnByte( 0 ),
273
1.74M
                                    eInForceArray( ParamClass::Unknown ) {}
274
                                FormulaByteToken( const FormulaByteToken& r ) :
275
17.8M
                                    FormulaToken( r ), mnByte( r.mnByte ),
276
17.8M
                                    eInForceArray( r.eInForceArray ) {}
277
278
17.8M
    virtual FormulaToken*       Clone() const override { return new FormulaByteToken(*this); }
279
107M
    sal_uInt8                   GetByte() const { return mnByte; }
280
1.90M
    void                        SetByte( sal_uInt8 n ) { mnByte = n; }
281
    virtual ParamClass          GetInForceArray() const override final;
282
    virtual void                SetInForceArray( ParamClass c ) override final;
283
    virtual bool                operator==( const FormulaToken& rToken ) const override;
284
};
285
286
287
// A special token for the FormulaAutoPilot only. Keeps a reference pointer of
288
// the token of which it was created for comparison.
289
class FORMULA_DLLPUBLIC FormulaFAPToken final : public FormulaToken
290
{
291
private:
292
            sal_uInt8           mnByte;
293
            FormulaTokenRef     pOrigToken;
294
public:
295
                                FormulaFAPToken( OpCode e, sal_uInt8 n, FormulaToken* p ) :
296
0
                                    FormulaToken(svFAP, e), mnByte(n), pOrigToken( p ) {}
297
                                FormulaFAPToken( const FormulaFAPToken& r ) :
298
0
                                    FormulaToken( r ), mnByte(r.mnByte), pOrigToken( r.pOrigToken ) {}
299
300
0
    virtual FormulaToken*       Clone() const override { return new FormulaFAPToken(*this); }
301
    FormulaToken*               GetFAPOrigToken() const;
302
0
    sal_uInt8                   GetByte() const { return mnByte; }
303
    virtual bool                operator==( const FormulaToken& rToken ) const override;
304
};
305
306
class FORMULA_DLLPUBLIC FormulaDoubleToken : public FormulaToken
307
{
308
private:
309
            double              fDouble;
310
public:
311
                                FormulaDoubleToken( double f ) :
312
1.44M
                                    FormulaToken( svDouble ), fDouble( f ) {}
313
                                FormulaDoubleToken( const FormulaDoubleToken& r ) :
314
118k
                                    FormulaToken( r ), fDouble( r.fDouble ) {}
315
316
118k
    virtual FormulaToken*       Clone() const override { return new FormulaDoubleToken(*this); }
317
2.73M
    double                      GetDouble() const { return fDouble; }
318
604k
    void                        SetDouble(double fValue) { fDouble = fValue; }
319
    virtual sal_Int16           GetDoubleType() const;     ///< always returns 0 for "not typed"
320
    virtual bool                operator==( const FormulaToken& rToken ) const override;
321
};
322
323
class FORMULA_DLLPUBLIC FormulaTypedDoubleToken final : public FormulaDoubleToken
324
{
325
private:
326
            sal_Int16           mnType;     /**< Can hold, for example, a value
327
                                              of SvNumFormatType, or by
328
                                              contract any other
329
                                              classification. */
330
public:
331
                                FormulaTypedDoubleToken( double f, sal_Int16 nType ) :
332
409k
                                    FormulaDoubleToken( f ), mnType( nType ) {}
333
                                FormulaTypedDoubleToken( const FormulaTypedDoubleToken& r ) :
334
213
                                    FormulaDoubleToken( r ), mnType( r.mnType ) {}
335
336
213
    virtual FormulaToken*       Clone() const override { return new FormulaTypedDoubleToken(*this); }
337
    virtual sal_Int16           GetDoubleType() const override;
338
    void                        SetDoubleType( sal_Int16 nType );
339
    virtual bool                operator==( const FormulaToken& rToken ) const override;
340
};
341
342
343
class FORMULA_DLLPUBLIC FormulaStringToken : public FormulaToken
344
{
345
    svl::SharedString maString;
346
protected:
347
    // constructor for FormulaStringOpToken to use
348
    FormulaStringToken( OpCode e, svl::SharedString r );
349
public:
350
    FormulaStringToken( svl::SharedString r );
351
    FormulaStringToken( const FormulaStringToken& r );
352
353
    virtual FormulaToken* Clone() const override;
354
13.7M
    const svl::SharedString & GetString() const { return maString; }
355
    void SetString( const svl::SharedString& rStr );
356
    virtual bool operator==( const FormulaToken& rToken ) const override;
357
};
358
359
360
/** Identical to FormulaStringToken, but with explicit OpCode instead of implicit
361
    ocPush, and an optional sal_uInt8 for ocBad tokens. */
362
class FORMULA_DLLPUBLIC FormulaStringOpToken final : public FormulaStringToken
363
{
364
    sal_uInt8           nByte;
365
    ParamClass          eInForceArray;
366
public:
367
    FormulaStringOpToken( OpCode e, svl::SharedString r );
368
    FormulaStringOpToken( const FormulaStringOpToken& r );
369
370
    virtual FormulaToken* Clone() const override;
371
80.7k
    sal_uInt8                   GetByte() const { return nByte; }
372
571k
    void                        SetByte( sal_uInt8 n ) { nByte = n; }
373
965k
    virtual ParamClass          GetInForceArray() const override final { return eInForceArray; }
374
0
    virtual void                SetInForceArray( ParamClass c ) override final { eInForceArray = c; }
375
    virtual bool operator==( const FormulaToken& rToken ) const override;
376
};
377
378
// FormulaStringNameToken
379
class FORMULA_DLLPUBLIC FormulaStringNameToken final : public FormulaToken
380
{
381
    svl::SharedString maString;
382
    bool mIsOptional;
383
public:
384
    FormulaStringNameToken(StackVar eTypeP, svl::SharedString r, bool isOptional = false);
385
    FormulaStringNameToken(const FormulaStringNameToken& r);
386
387
    virtual FormulaToken* Clone() const override;
388
0
    const svl::SharedString& GetString() const { return maString; }
389
0
    bool GetIsOptional() const { return mIsOptional; }
390
0
    void SetIsOptional(bool isOptional) { mIsOptional = isOptional; }
391
    virtual bool operator==(const FormulaToken& rToken) const override;
392
};
393
394
// A data-pilot field name is stored in a FormulaStringNameToken too, so both
395
// svStringName and svDPFieldName identify one.
396
inline const FormulaStringNameToken* GetStringNameToken(const FormulaToken* p)
397
0
{
398
0
    const StackVar eType = p->GetType();
399
0
    if (eType == svStringName || eType == svDPFieldName)
400
0
    {
401
0
        assert(dynamic_cast<const FormulaStringNameToken*>(p));
402
0
        return static_cast<const FormulaStringNameToken*>(p);
403
0
    }
404
0
    return nullptr;
405
0
}
406
407
408
class FORMULA_DLLPUBLIC FormulaIndexToken final : public FormulaToken
409
{
410
private:
411
            sal_uInt16          nIndex;
412
            sal_Int16           mnSheet;
413
public:
414
                                FormulaIndexToken( OpCode e, sal_uInt16 n, sal_Int16 nSheet = -1 ) :
415
36.7k
                                    FormulaToken(  svIndex, e ), nIndex( n ), mnSheet( nSheet ) {}
416
                                FormulaIndexToken( const FormulaIndexToken& r ) :
417
8.97k
                                    FormulaToken( r ), nIndex( r.nIndex ), mnSheet( r.mnSheet ) {}
418
419
8.97k
    virtual FormulaToken*       Clone() const override { return new FormulaIndexToken(*this); }
420
    sal_uInt16                  GetIndex() const;
421
    void                        SetIndex( sal_uInt16 n );
422
    sal_Int16                   GetSheet() const;
423
    void                        SetSheet( sal_Int16 n );
424
    virtual bool                operator==( const FormulaToken& rToken ) const override;
425
};
426
427
428
class FORMULA_DLLPUBLIC FormulaExternalToken final : public FormulaToken
429
{
430
private:
431
    sal_uInt8         mnByte;
432
    ParamClass        eInForceArray;
433
    OUString          aExternal;
434
public:
435
                                FormulaExternalToken( OpCode e, sal_uInt8 n, OUString  r ) :
436
908
                                    FormulaToken( svExternal, e), mnByte(n), eInForceArray( ParamClass::Unknown ),
437
908
                                    aExternal(std::move( r )) {}
438
                                FormulaExternalToken( OpCode e, OUString  r ) :
439
482k
                                    FormulaToken( svExternal, e), mnByte(0), eInForceArray( ParamClass::Unknown ),
440
482k
                                    aExternal(std::move( r )) {}
441
                                FormulaExternalToken( const FormulaExternalToken& r ) :
442
64.1k
                                    FormulaToken( r ), mnByte(r.mnByte), eInForceArray(r.eInForceArray), aExternal( r.aExternal ) {}
443
444
64.1k
    virtual FormulaToken*       Clone() const override { return new FormulaExternalToken(*this); }
445
    const OUString&             GetExternal() const;
446
950k
    sal_uInt8                   GetByte() const { return mnByte; }
447
399k
    void                        SetByte( sal_uInt8 n ) { mnByte = n; }
448
873k
    virtual ParamClass          GetInForceArray() const override final { return eInForceArray; }
449
0
    virtual void                SetInForceArray( ParamClass c ) override final { eInForceArray = c; }
450
    virtual bool                operator==( const FormulaToken& rToken ) const override;
451
};
452
453
454
class FORMULA_DLLPUBLIC FormulaMissingToken final : public FormulaToken
455
{
456
public:
457
                                FormulaMissingToken() :
458
997k
                                    FormulaToken( svMissing,ocMissing ) {}
459
                                FormulaMissingToken( const FormulaMissingToken& r ) :
460
4.61k
                                    FormulaToken( r ) {}
461
462
4.61k
    virtual FormulaToken*       Clone() const override { return new FormulaMissingToken(*this); }
463
    virtual bool                operator==( const FormulaToken& rToken ) const override;
464
};
465
466
class FORMULA_DLLPUBLIC FormulaJumpToken final : public FormulaToken
467
{
468
private:
469
            std::unique_ptr<short[]>
470
                                pJump;
471
            ParamClass          eInForceArray;
472
public:
473
                                FormulaJumpToken( OpCode e, short const * p ) :
474
127k
                                    FormulaToken( formula::svJump , e),
475
127k
                                    eInForceArray( ParamClass::Unknown)
476
127k
                                {
477
127k
                                    pJump.reset( new short[ p[0] + 1 ] );
478
127k
                                    memcpy( pJump.get(), p, (p[0] + 1) * sizeof(short) );
479
127k
                                }
480
                                FormulaJumpToken( const FormulaJumpToken& r ) :
481
59.2k
                                    FormulaToken( r ),
482
59.2k
                                    eInForceArray( r.eInForceArray)
483
59.2k
                                {
484
59.2k
                                    pJump.reset( new short[ r.pJump[0] + 1 ] );
485
59.2k
                                    memcpy( pJump.get(), r.pJump.get(), (r.pJump[0] + 1) * sizeof(short) );
486
59.2k
                                }
487
    virtual                     ~FormulaJumpToken() override;
488
98.4k
    const short*                GetJump() const { return pJump.get(); }
489
529k
    short*                      GetJump() { return pJump.get(); }
490
    virtual bool                operator==( const formula::FormulaToken& rToken ) const override;
491
59.2k
    virtual FormulaToken*       Clone() const override { return new FormulaJumpToken(*this); }
492
    virtual ParamClass          GetInForceArray() const override;
493
    virtual void                SetInForceArray( ParamClass c ) override;
494
};
495
496
497
class FORMULA_DLLPUBLIC FormulaUnknownToken final : public FormulaToken
498
{
499
public:
500
                                FormulaUnknownToken( OpCode e ) :
501
0
                                    FormulaToken( svUnknown, e ) {}
502
                                FormulaUnknownToken( const FormulaUnknownToken& r ) :
503
0
                                    FormulaToken( r ) {}
504
505
0
    virtual FormulaToken*       Clone() const override { return new FormulaUnknownToken(*this); }
506
    virtual bool                operator==( const FormulaToken& rToken ) const override;
507
};
508
509
510
class FORMULA_DLLPUBLIC FormulaErrorToken final : public FormulaToken
511
{
512
         FormulaError          nError;
513
public:
514
                                FormulaErrorToken() :
515
80
                                    FormulaToken( svError ), nError( FormulaError::IllegalChar ) {}
516
                                FormulaErrorToken( FormulaError nErr ) :
517
303k
                                    FormulaToken( svError ), nError( nErr) {}
518
                                FormulaErrorToken( const FormulaErrorToken& r ) :
519
2.51k
                                    FormulaToken( r ), nError( r.nError) {}
520
521
2.51k
    virtual FormulaToken*       Clone() const override { return new FormulaErrorToken(*this); }
522
    FormulaError                GetError() const;
523
    void                        SetError( FormulaError nErr );
524
    virtual bool                operator==( const FormulaToken& rToken ) const override;
525
526
    // we do some performance stuff in ScInterpreter::PushError that needs this
527
    FormulaErrorToken& operator=( const FormulaErrorToken& rOther )
528
0
    {
529
0
        nError = rOther.nError;
530
0
        return *this;
531
0
    }
532
};
533
534
535
// Only created from within the interpreter
536
class FORMULA_DLLPUBLIC FormulaCallableToken final : public FormulaToken
537
{
538
private:
539
            FormulaCallableRef  mpCallable;
540
public:
541
                                FormulaCallableToken( FormulaCallableRef pCallable ) :
542
18.8k
                                    FormulaToken( svCallable ), mpCallable(pCallable) {}
543
373
                                FormulaCallableToken( const FormulaCallableToken& r ) = default;
544
373
    virtual FormulaToken*       Clone() const override { return new FormulaCallableToken(*this); }
545
            FormulaCallableRef  GetCallable() const;
546
    virtual bool                operator==( const FormulaToken& rToken ) const override;
547
};
548
549
550
} // formula
551
552
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */