source: cpp/frams/param/param.cpp @ 1186

Last change on this file since 1186 was 1186, checked in by Maciej Komosinski, 16 months ago

For consistency with other numerical types, unlimited string length is now indicated by "-1" as the second value of the definition of the "s" property

  • Property svn:eol-style set to native
File size: 35.5 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2022  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include <stdio.h>
6#include <ctype.h>
7
8#include "param.h"
9#include <frams/util/extvalue.h>
10#include "common/log.h"
11#include <frams/util/sstringutils.h>
12#include <common/virtfile/stringfile.h>
13
14#ifdef _DEBUG
15//for sanityCheck - mutable param detection
16#include "mutparamiface.h"
17#endif
18
19//#define SAVE_ALL_NAMES
20#define SAVE_SELECTED_NAMES
21#define WARN_MISSING_NAME
22
23char MakeCodeGuardHappy;
24
25ParamEntry empty_paramtab[] =
26{ { "Empty", 1, 0, "Empty", }, { 0, 0, 0, }, };
27
28/** return: true if tilde was found, false if finished at EOF */
29static bool readUntilTilde(VirtFILE *f, SString &s)
30{
31        SString temp;
32        int z;
33        char last_char = 0;
34        bool tilde_found = false;
35        while ((z = f->Vgetc()) != EOF)
36        {
37                if (z == '~')
38                        if (last_char != '\\') { tilde_found = true; break; }
39                last_char = (char)z;
40                temp += last_char;
41        }
42        s = temp;
43        return tilde_found;
44}
45
46static const char *strchrlimit(const char *t, int ch, const char *limit)
47{
48        if (limit < t) return NULL;
49        return (const char*)memchr((const void*)t, ch, limit - t);
50}
51
52
53Param2ParamCopy::Param2ParamCopy(ParamInterface& schema, const std::initializer_list<const char*> names)
54{
55        for (const char* n : names)
56        {
57                int i = schema.findId(n);
58                if (i >= 0)
59                        fields.push_back(i);
60                else
61                        logPrintf("Param2ParamCopy", "findId", LOG_CRITICAL, "Can't find '%s.%s'", schema.getName(), n);
62        }
63}
64
65void Param2ParamCopy::operator()(const ParamInterface& from, ParamInterface& to)
66{
67        ExtValue tmp;
68        for (int i : fields)
69        {
70                ((ParamInterface&)from).get(i, tmp);
71                to.set(i, tmp);
72        }
73}
74
75
76void ParamInterface::copyFrom(ParamInterface *src)
77{
78        int n = getPropCount();
79        ExtValue v;
80        int j;
81        for (int i = 0; i < n; i++)
82                if ((!(flags(i) & PARAM_READONLY))
83                        && (*type(i) != 'p'))
84                {
85                        j = src->findId(id(i));
86                        if (j < 0) continue;
87                        src->get(j, v);
88                        set(i, v);
89                }
90}
91
92void ParamInterface::quickCopyFrom(ParamInterface *src)
93{
94        int n = getPropCount();
95        ExtValue v;
96        for (int i = 0; i < n; i++)
97                if ((!(flags(i) & PARAM_READONLY))
98                        && (*type(i) != 'p'))
99                {
100                        src->get(i, v);
101                        set(i, v);
102                }
103}
104
105int ParamInterface::getMinMaxInt(int prop, paInt& minumum, paInt& maximum, paInt &def)
106{
107        return getMinMaxIntFromTypeDef(type(prop), minumum, maximum, def);
108}
109
110int ParamInterface::getMinMaxDouble(int prop, double& minumum, double& maximum, double& def)
111{
112        return getMinMaxDoubleFromTypeDef(type(prop), minumum, maximum, def);
113}
114
115int ParamInterface::getMinMaxString(int prop, int& minumum, int& maximum, SString& def)
116{
117        return getMinMaxStringFromTypeDef(type(prop), minumum, maximum, def);
118}
119
120int ParamInterface::getMinMaxIntFromTypeDef(const char* t, paInt& minumum, paInt& maximum, paInt &def)
121{
122        while (*t) if (*t == ' ') break; else t++;
123        return sscanf(t, PA_INT_SCANF " " PA_INT_SCANF " " PA_INT_SCANF, &minumum, &maximum, &def);
124}
125
126int ParamInterface::getMinMaxDoubleFromTypeDef(const char* t, double& minumum, double& maximum, double& def)
127{
128        while (*t) if (*t == ' ') break; else t++;
129        return sscanf(t, "%lg %lg %lg", &minumum, &maximum, &def);
130}
131
132int ParamInterface::getMinMaxStringFromTypeDef(const char* t, int& minumum, int& maximum, SString& def)
133{
134        while (*t) if (*t == ' ') break; else t++;
135        int ret = sscanf(t, "%d %d", &minumum, &maximum);
136        def = SString::empty();
137        if (ret == 2)
138        {
139                while (*t == ' ') t++;
140                for (int skip_fields = 2; skip_fields > 0; skip_fields--)
141                {
142                        while (*t) if (*t == ' ') break; else t++;
143                        while (*t == ' ') t++;
144                }
145                if (*t)
146                {
147                        const char* end = strchr(t, '~');
148                        if (!end)
149                                end = t + strlen(t);
150                        while ((end > t) && (end[-1] == ' ')) end--;
151                        def = SString(t, end - t);
152                }
153                return 3;
154        }
155        else
156                return ret;
157}
158
159void ParamInterface::setDefault()
160{
161        for (int i = 0; i < getPropCount(); i++)
162                setDefault(i);
163}
164
165void ParamInterface::setMin()
166{
167        for (int i = 0; i < getPropCount(); i++)
168                setMin(i);
169}
170
171void ParamInterface::setMax()
172{
173        for (int i = 0; i < getPropCount(); i++)
174                setMax(i);
175}
176
177void ParamInterface::setDefault(int i)
178{
179        const char *t = type(i);
180        switch (*t)
181        {
182        case 'f':
183        {
184                double mn = 0, mx = 0, def = 0;
185                if (getMinMaxDoubleFromTypeDef(t, mn, mx, def) < 3) def = mn;
186                setDouble(i, def);
187        }
188        break;
189        case 'd':
190        {
191                paInt mn = 0, mx = 0, def = 0;
192                if (getMinMaxIntFromTypeDef(t, mn, mx, def) < 3) def = mn;
193                setInt(i, def);
194        }
195        break;
196        case 's': case 'x':
197        {
198                int mn, mx; SString def;
199                getMinMaxStringFromTypeDef(t, mn, mx, def);
200                if (*t == 's')
201                        setString(i, def);
202                else
203                {
204                        if (def.length() > 0) setExtValue(i, ExtValue(def)); else setExtValue(i, ExtValue::empty());
205                }
206        }
207        break;
208        case 'o':
209                setObject(i, ExtObject::empty());
210                break;
211        }
212}
213
214void ParamInterface::setMin(int i)
215{
216        const char *t = type(i);
217        switch (*t)
218        {
219        case 'f':
220        {
221                double mn = 0, mx = 0, def = 0;
222                getMinMaxDoubleFromTypeDef(t, mn, mx, def);
223                setDouble(i, mn);
224        }
225        break;
226        case 'd':
227        {
228                paInt mn = 0, mx = 0, def = 0;
229                getMinMaxIntFromTypeDef(t, mn, mx, def);
230                setInt(i, mn);
231        }
232        break;
233        default: setFromString(i, "", false);
234        }
235}
236
237void ParamInterface::setMax(int i)
238{
239        const char *t = type(i);
240        switch (*t)
241        {
242        case 'f':
243        {
244                double mn = 0, mx = 0, def = 0;
245                getMinMaxDoubleFromTypeDef(t, mn, mx, def);
246                setDouble(i, mx);
247        }
248        break;
249        case 'd':
250        {
251                paInt mn = 0, mx = 0, def = 0;
252                getMinMaxIntFromTypeDef(t, mn, mx, def);
253                setInt(i, mx);
254        }
255        break;
256        default: setFromString(i, "", false);
257        }
258}
259
260SString ParamInterface::getStringById(const char*prop)
261{
262        int i = findId(prop); if (i >= 0) return getString(i); else return SString();
263}
264paInt ParamInterface::getIntById(const char*prop)
265{
266        int i = findId(prop); if (i >= 0) return getInt(i); else return 0;
267}
268double ParamInterface::getDoubleById(const char*prop)
269{
270        int i = findId(prop); if (i >= 0) return getDouble(i); else return 0;
271}
272ExtObject ParamInterface::getObjectById(const char*prop)
273{
274        int i = findId(prop); if (i >= 0) return getObject(i); else return ExtObject();
275}
276ExtValue ParamInterface::getExtValueById(const char*prop)
277{
278        int i = findId(prop); if (i >= 0) return getExtValue(i); else return ExtValue();
279}
280
281int ParamInterface::setIntById(const char* prop, paInt v)
282{
283        int i = findId(prop); if (i >= 0) return setInt(i, v); else return PSET_NOPROPERTY;
284}
285int ParamInterface::setDoubleById(const char* prop, double v)
286{
287        int i = findId(prop); if (i >= 0) return setDouble(i, v); else return PSET_NOPROPERTY;
288}
289int ParamInterface::setStringById(const char* prop, const SString &v)
290{
291        int i = findId(prop); if (i >= 0) return setString(i, v); else return PSET_NOPROPERTY;
292}
293int ParamInterface::setObjectById(const char* prop, const ExtObject &v)
294{
295        int i = findId(prop); if (i >= 0) return setObject(i, v); else return PSET_NOPROPERTY;
296}
297int ParamInterface::setExtValueById(const char* prop, const ExtValue &v)
298{
299        int i = findId(prop); if (i >= 0) return setExtValue(i, v); else return PSET_NOPROPERTY;
300}
301int ParamInterface::setById(const char* prop, const ExtValue &v)
302{
303        int i = findId(prop); if (i >= 0) return set(i, v); else return PSET_NOPROPERTY;
304}
305
306int ParamInterface::saveMultiLine(VirtFILE* f, const char* altname, bool force)
307{
308        const char *p;
309        SString ws;
310        int err = 0, i;
311        bool withname = false;
312        if ((altname == NULL) || (altname[0] != 0))
313        {
314                err |= (f->Vputs(altname ? altname : getName()) == EOF);
315                err |= (f->Vputs(":\n") == EOF);
316                withname = true;
317        }
318        for (i = 0; p = id(i); i++)
319                err |= saveprop(f, i, p, force);
320        if (withname)
321                err |= (f->Vputs("\n") == EOF);
322        return err;
323}
324
325const char* ParamInterface::SERIALIZATION_PREFIX = "@Serialized:";
326
327int ParamInterface::saveprop(VirtFILE* f, int i, const char* p, bool force)
328{
329        if ((flags(i) & PARAM_DONTSAVE) && (!force)) return 0;
330        const char *typ = type(i);
331        if (*typ == 'p') return 0;
332
333        const char *t, *w;
334        SString ws;
335        int err = 0, cr;
336
337        err |= (f->Vputs(p) == EOF); f->Vputc(':');
338        cr = 0;
339        if ((*typ == 'x') || (*typ == 'o'))
340        {
341                ExtValue ex;
342                get(i, ex);
343                ws = SString(SERIALIZATION_PREFIX) + ex.serialize(NativeSerialization);
344        }
345        else
346                ws = get(i);
347        quoteTilde(ws);
348        w = ws.c_str();
349        if (ws.length() > 50) cr = 1;
350        else for (t = w; *t; t++) if ((*t == 10) || (*t == 13)) { cr = 1; break; }
351        if (cr) f->Vputs("~\n");
352        err |= (f->Vputs(w) == EOF);
353        err |= (f->Vputs(cr ? "~\n" : "\n") == EOF);
354        return err;
355}
356
357SString ParamInterface::nameDotProperty(int prop)
358{
359        SString ret = getName();
360        ret += ".";
361        ret += id(prop);
362        return ret;
363}
364
365SString ParamInterface::nameDotPropertyForMessages(int prop)
366{
367        SString name_dot_prop = nameDotProperty(prop);
368        if (strcmp(getName(), getLongName()) == 0)
369        {
370                if (strcmp(id(prop), name(prop)) == 0)
371                        return name_dot_prop;
372                else
373                        return SString("'") + name(prop) + "': " + name_dot_prop;
374        }
375        else
376                return SString("'") + name(prop) + "' in '" + getLongName() + "': " + name_dot_prop;
377}
378
379int SimpleAbstractParam::isequal(int i, void* defdata)
380{ // defdata->member == object->member ?
381        void *backup = object;
382        switch (type(i)[0])
383        {
384        case 'd':
385        {
386                select(defdata);
387                paInt x = getInt(i);
388                select(backup);
389                return x == getInt(i);
390        }
391        case 'f':
392        {
393                select(defdata);
394                double x = getDouble(i);
395                select(backup);
396                return x == getDouble(i);
397        }
398        case 's':
399        {
400                select(defdata);
401                SString x = getString(i);
402                select(backup);
403                return x == getString(i);
404        }
405        }
406        return 1;
407}
408
409void SimpleAbstractParam::saveSingleLine(SString& f, void *defdata, bool addcr, bool all_names)
410{ // defdata!=NULL -> does not save default values
411        const char *p;
412        int i;
413        int needlabel = 0;
414        int first = 1;
415        SString val;
416        SString t;
417        int fl;
418        // t+=SString(getName()); t+=':';
419        for (i = 0; p = id(i); i++)
420                if ((!((fl = flags(i)) & PARAM_DONTSAVE)) && type(i)[0]!='p')
421                {
422                        if (defdata && isequal(i, defdata))
423                                needlabel = 1;
424                        else
425                        {
426                                if (!first) t += ", ";
427#ifndef SAVE_ALL_NAMES
428#ifdef SAVE_SELECTED_NAMES
429                                if (needlabel || all_names || !(fl & PARAM_CANOMITNAME))
430#else
431                                if (needlabel)
432#endif
433#endif
434                                {
435                                        t += p; t += "="; needlabel = 0;
436                                }
437                                if (type(i)[0] == 's')
438                                { // string - special case
439                                        SString str = getString(i);
440                                        if (strContainsOneOf(str.c_str(), ", \\\n\r\t\""))
441                                        {
442                                                t += "\"";
443                                                sstringQuote(str);
444                                                t += str;
445                                                t += "\"";
446                                        }
447                                        else
448                                                t += str;
449                                }
450                                else
451                                        t += get(i);
452                                first = 0;
453                        }
454                }
455        if (addcr)
456                t += "\n";
457        f += t;
458}
459
460static void closingTildeError(ParamInterface *pi, VirtFILE *file, int field_index)
461{
462        SString fileinfo;
463        const char* fname = file->VgetPath();
464        if (fname != NULL)
465                fileinfo = SString::sprintf(" while reading from '%s'", fname);
466        SString field;
467        if (field_index >= 0)
468                field = pi->nameDotPropertyForMessages(field_index);
469        else
470                field = SString::sprintf("unknown property of '%s'", pi->getName());
471        logPrintf("ParamInterface", "load", LOG_WARN, "Closing '~' (tilde) not found in %s%s", field.c_str(), fileinfo.c_str());
472}
473
474template<typename T> void messageOnExceedRange(SimpleAbstractParam *pi, int i, int setflags, T valuetoset) ///< prints a warning when setflags indicates that allowed param range has been exceeded during set
475{
476        if (setflags & (PSET_HITMIN | PSET_HITMAX))
477        {
478                ExtValue v(valuetoset);
479                pi->messageOnExceedRange(i, setflags, v);
480        }
481}
482
483void SimpleAbstractParam::messageOnExceedRange(int i, int setflags, ExtValue& valuetoset) ///< prints a warning when setflags indicates that allowed param range has been exceeded during set
484{
485        if (setflags & (PSET_HITMIN | PSET_HITMAX))
486        {
487                SString svaluetoset = valuetoset.getString(); //converts any type to SString
488                SString actual = get(i);
489                bool s_type = type(i)[0] == 's';
490                bool show_length = valuetoset.getType() == TString;
491                const char* quote = (valuetoset.getType() == TString) ? "\"" : "'";
492                logPrintf("Param", "set", LOG_WARN, "Setting %s = %s exceeded allowed range (too %s). %s to %s.",
493                        nameDotPropertyForMessages(i).c_str(),
494                        ::sstringDelimitAndShorten(svaluetoset, 30, show_length, quote, quote).c_str(),
495                        (setflags & PSET_HITMAX) ? (s_type ? "long" : "big") : "small", s_type ? "Truncated" : "Adjusted",
496                        ::sstringDelimitAndShorten(actual, 30, show_length, quote, quote).c_str()
497                );
498        }
499}
500
501int ParamInterface::load(FileFormat format, VirtFILE* f, LoadOptions *options)
502{
503        LoadOptions default_options;
504        if (options == NULL)
505                options = &default_options;
506        switch (format)
507        {
508        case FormatMultiLine:
509                return loadMultiLine(f, *options);
510
511        case FormatSingleLine:
512        {
513                StringFILE *sf = dynamic_cast<StringFILE*>(f);
514                SString s;
515                if (sf)
516                {
517                        s = sf->getString().c_str();
518                        options->offset += sf->Vtell();
519                }
520                else
521                {
522                        if (!loadSStringLine(f, s))
523                                return -1;
524                }
525                return loadSingleLine(s, *options);
526        }
527        }
528        return -1;
529}
530
531int ParamInterface::load(FileFormat format, const SString &s, LoadOptions *options)
532{
533        LoadOptions default_options;
534        if (options == NULL)
535                options = &default_options;
536        switch (format)
537        {
538        case FormatMultiLine:
539        {
540                string std_string(s.c_str());
541                StringFILE f(std_string);
542                return loadMultiLine(&f, *options);
543        }
544
545        case FormatSingleLine:
546                return loadSingleLine(s, *options);
547        }
548        return -1;
549}
550
551int ParamInterface::loadMultiLine(VirtFILE* f, LoadOptions &options)
552{
553        SString buf;
554        int i;
555        const char *p, *p0;
556        int p_len;
557        bool loaded;
558        int fields_loaded = 0;
559        int unexpected_line = 0;
560        vector<bool> seen;
561        seen.resize(getPropCount());
562        if ((i = findId("beforeLoad")) >= 0)
563                call(i, NULL, NULL);
564        while (((!options.abortable) || (!*options.abortable)) && loadSStringLine(f, buf))
565        {
566                if (options.linenum) (*options.linenum)++;
567                const char* t = buf.c_str();
568                p0 = t; while (isblank(*p0)) p0++;
569                if (!*p0) break;
570                if (p0[0] == '#') { unexpected_line = 0; continue; }
571                p = strchr(p0, ':');
572                if (!p)
573                {
574                        switch (unexpected_line)
575                        {
576                        case 0:
577                                logPrintf("ParamInterface", "load", LOG_WARN, "Ignored unexpected line %s while reading object '%s'",
578                                        options.linenum ?
579                                        SString::sprintf("%d", *options.linenum).c_str()
580                                        : SString::sprintf("'%s'", p0).c_str(),
581                                        getName());
582                                break;
583                        case 1:
584                                logPrintf("ParamInterface", "load", LOG_WARN, "The following line(s) were also unexpected and were ignored");
585                                break;
586                        }
587                        unexpected_line++;
588                        continue;
589                }
590                unexpected_line = 0;
591                p_len = (int)(p - p0);
592                loaded = false;
593                if (p_len && ((i = findIdn(p0, p_len)) >= 0))
594                {
595                        if (seen[i])
596                        {
597                                SString fileinfo;
598                                const char* fname = f->VgetPath();
599                                if (fname != NULL)
600                                {
601                                        fileinfo = SString::sprintf(" while reading from '%s'", fname);
602                                        if (options.linenum)
603                                                fileinfo += SString::sprintf(" (line %d)", *options.linenum);
604                                }
605                                logPrintf("ParamInterface", "load", LOG_WARN, "Multiple '%s.%s' properties found%s", getName(), id(i), fileinfo.c_str());
606                        }
607                        else
608                                seen[i] = true;
609                        if (!(flags(i) & PARAM_DONTLOAD))
610                        {
611                                if (p0[p_len + 1] == '~')
612                                {
613                                        SString s;
614                                        if (!readUntilTilde(f, s))
615                                                closingTildeError(this, f, i);
616                                        int lfcount = 1;
617                                        const char* tmp = s.c_str();
618                                        while (tmp)
619                                                if ((tmp = strchr(tmp, '\n')))
620                                                {
621                                                        lfcount++; tmp++;
622                                                }
623                                        removeCR(s);
624                                        int ch; while ((ch = f->Vgetc()) != EOF) if (ch == '\n') break;
625                                        unquoteTilde(s);
626                                        if (options.linenum && (flags(i) & PARAM_LINECOMMENT))
627                                                s = SString::sprintf("@file %s\n@line %d\n", f->VgetPath(), *options.linenum + 1) + s;
628                                        setFromString(i, s.c_str(), false);
629                                        if (options.linenum)
630                                                (*options.linenum) += lfcount;
631                                }
632                                else
633                                {
634                                        SString s = SString(p0 + p_len + 1);
635                                        unquoteTilde(s);
636                                        setFromString(i, s.c_str(), false);
637                                }
638                                fields_loaded++;
639                                loaded = true;
640                        }
641                }
642                else if (options.warn_unknown_fields)
643                {
644                        SString name(p0, p_len);
645                        logPrintf("ParamInterface", "load", LOG_WARN, "Ignored unknown property '%s.%s'", getName(), name.c_str());
646                }
647
648                if ((!loaded) && (p0[p_len + 1] == '~'))
649                { // eat unrecognized multiline field
650                        SString s;
651                        if (!readUntilTilde(f, s))
652                                closingTildeError(this, f, -1);
653                        if (options.linenum)
654                        {
655                                const char* tmp = s.c_str();
656                                int lfcount = 1;
657                                while (tmp)
658                                        if ((tmp = strchr(tmp, '\n')))
659                                        {
660                                                lfcount++; tmp++;
661                                        }
662                                (*options.linenum) += lfcount;
663                        }
664                        int ch; while ((ch = f->Vgetc()) != EOF) if (ch == '\n') break;
665                }
666        }
667        if ((i = findId("afterLoad")) >= 0)
668                call(i, NULL, NULL);
669        return fields_loaded;
670}
671
672
673/*
674SString SimpleAbstractParam::getString(int i)
675{
676char *t;
677switch (*(t=type(i)))
678{
679case 'd':
680{
681for (i=atol(get(i));i>=0;i--) if (t) t=strchr(t+1,'~');
682if (t)
683{
684t++;
685char *t2=strchr(t,'~');
686if (!t2) t2=t+strlen(t);
687SString str;
688strncpy(str.directWrite(t2-t),t,t2-t);
689str.endWrite(t2-t);
690return str;
691}
692}
693}
694return get(i);
695}
696*/
697
698int ParamInterface::findId(const char* n)
699{
700        int i; const char *p;
701        for (i = 0; p = id(i); i++) if (!strcmp(n, p)) return i;
702        return -1;
703}
704
705int ParamInterface::findIdn(const char* naz, int n)
706{
707        int i; const char *p;
708        for (i = 0; p = id(i); i++) if ((!strncmp(naz, p, n)) && (!p[n])) return i;
709        return -1;
710}
711
712int ParamInterface::findGroupId(const char* name)
713{
714for(int i=0;i<getGroupCount();i++)
715        if (!strcmp(grname(i),name))
716                return i;
717return -1;
718}
719
720void ParamInterface::get(int i, ExtValue &ret)
721{
722        switch (type(i)[0])
723        {
724        case 'd':       ret.setInt(getInt(i)); break;
725        case 'f':       ret.setDouble(getDouble(i)); break;
726        case 's':       ret.setString(getString(i)); break;
727        case 'o':       ret.setObject(getObject(i)); break;
728        case 'x':       ret = getExtValue(i); break;
729        default: logPrintf("ParamInterface", "get", LOG_ERROR, "%s is not a property", nameDotPropertyForMessages(i).c_str());
730        }
731}
732
733int ParamInterface::setIntFromString(int i, const char* str, bool strict)
734{
735        paInt value;
736        if (!ExtValue::parseInt(str, value, strict, true))
737        {
738                paInt mn, mx, def;
739                if (getMinMaxInt(i, mn, mx, def) >= 3)
740                        return setInt(i, def) | PSET_PARSEFAILED;
741                else
742                        return setInt(i, (paInt)0) | PSET_PARSEFAILED;
743        }
744        else
745                return setInt(i, value);
746}
747
748int ParamInterface::setDoubleFromString(int i, const char* str)
749{
750        double value;
751        if (!ExtValue::parseDouble(str, value, true))
752        {
753                double mn, mx, def;
754                if (getMinMaxDouble(i, mn, mx, def) >= 3)
755                        return setDouble(i, def) | PSET_PARSEFAILED;
756                else
757                        return setDouble(i, (double)0) | PSET_PARSEFAILED;
758        }
759        else
760                return setDouble(i, value);
761}
762
763int ParamInterface::set(int i, const ExtValue &v)
764{
765        switch (type(i)[0])
766        {
767        case 'd':
768                if ((v.type == TInt) || (v.type == TDouble)) return setInt(i, v.getInt());
769                else
770                {
771                        if (v.type == TObj)
772                        {
773                                logPrintf("ParamInterface", "set", LOG_ERROR, "Setting int %s from object reference (%s)", nameDotPropertyForMessages(i).c_str(), v.getString().c_str());
774                                return 0;
775                        }
776                        else
777                                return setIntFromString(i, v.getString().c_str(), false);
778                }
779        case 'f':
780                if ((v.type == TInt) || (v.type == TDouble)) return setDouble(i, v.getDouble());
781                else
782                {
783                        if (v.type == TObj)
784                        {
785                                logPrintf("ParamInterface", "set", LOG_ERROR, "Setting float %s from object reference (%s)", nameDotPropertyForMessages(i).c_str(), v.getString().c_str());
786                                return 0;
787                        }
788                        else
789                                return setDoubleFromString(i, v.getString().c_str());
790                }
791        case 's': { SString t = v.getString(); return setString(i, t); }
792        case 'o':
793                if ((v.type != TUnknown) && (v.type != TObj))
794                        logPrintf("ParamInterface", "set", LOG_ERROR, "Setting object %s from %s", nameDotPropertyForMessages(i).c_str(), v.typeAndValue().c_str());
795                else
796                        return setObject(i, v.getObject());
797                break;
798        case 'x': return setExtValue(i, v);
799        default: logPrintf("ParamInterface", "set", LOG_ERROR, "%s is not a property", nameDotPropertyForMessages(i).c_str());
800        }
801        return 0;
802}
803
804int ParamInterface::setFromString(int i, const char *v, bool strict)
805{
806        char typ = type(i)[0];
807        switch (typ)
808        {
809        case 'd': return setIntFromString(i, v, strict);
810        case 'f': return setDoubleFromString(i, v);
811        case 's': { SString t(v); return setString(i, t); }
812        case 'x': case 'o':
813        {
814                ExtValue e;
815                const char* after;
816                if (!strncmp(v, SERIALIZATION_PREFIX, strlen(SERIALIZATION_PREFIX)))
817                {
818                        after = e.deserialize(v + strlen(SERIALIZATION_PREFIX));
819                        if ((after == NULL) || (*after))
820                        {
821                                logPrintf("ParamInterface", "set", LOG_ERROR, "serialization format mismatch in %s.%s", (getName() ? getName() : "<Unknown>"), id(i));
822                                e.setEmpty();
823                        }
824                }
825                else if ((after = e.parseNumber(v)) && (*after == 0)) //consumed the whole string
826                {
827                        //OK!
828                }
829                else
830                {
831                        e.setString(SString(v));
832                }
833                if (typ == 'x')
834                        return setExtValue(i, e);
835                else
836                        return setObject(i, e.getObject());
837        }
838        }
839        return 0;
840}
841
842SString ParamInterface::getText(int i) //find the current enum text or call get(i) if not enum
843{
844        const char *t;
845        if (((*(t = type(i))) == 'd') && (strchr(t, '~') != NULL)) //type is int and contains enum labels
846        {
847                paInt mn, mx, def;
848                int value = getInt(i);
849                if (getMinMaxIntFromTypeDef(t, mn, mx, def) >= 2)
850                {
851                        if (value > mx)
852                                return get(i);//unexpected value of out bounds (should never happen) -> fallback
853                        value -= mn;
854                }
855                if (value < 0) return get(i); //unexpected value of out bounds (should never happen) -> fallback
856                // now value is 0-based index of ~text
857                for (; value >= 0; value--) if (t) t = strchr(t + 1, '~'); else break;
858                if (t) // found n-th ~text in type description (else: not enough ~texts in type description)
859                {
860                        t++;
861                        const char *t2 = strchr(t, '~');
862                        if (!t2) t2 = t + strlen(t);
863                        return SString(t, (int)(t2 - t));
864                }
865        }
866        return get(i); //fallback - return int value as string
867}
868
869SString ParamInterface::get(int i)
870{
871        switch (type(i)[0])
872        {
873        case 'd': return SString::valueOf(getInt(i));
874        case 'f': return SString::valueOf(getDouble(i));
875        case 's': return getString(i);
876        }
877        ExtValue v;
878        get(i, v);
879        return v.getString();
880}
881
882bool ParamInterface::isValidTypeDescription(const char* t)
883{
884        if (t == NULL) return false;
885        if (*t == 0) return false;
886        if (strchr("dfsoxp", *t) == NULL) return false;
887        switch (*t)
888        {
889        case 'd':
890        {
891                paInt a, b, c;
892                int have = getMinMaxIntFromTypeDef(t, a, b, c);
893                if (have == 1) return false;
894                if ((have >= 2) && (b < a) && (a != 0) && (b != -1)) return false; // max<min meaning 'undefined' is only allowed as "d 0 -1"
895        }
896        break;
897        case 'f':
898        {
899                double a, b, c;
900                int have = getMinMaxDoubleFromTypeDef(t, a, b, c);
901                if (have == 1) return false;
902                if ((have >= 2) && (b < a) && (a != 0) && (b != -1)) return false; // max<min meaning 'undefined' is only allowed as "f 0 -1"
903        }
904        break;
905        case 's':
906        {
907                int a, b; SString c;
908                int have = getMinMaxStringFromTypeDef(t, a, b, c);
909                //if (have == 1) return false; //not sure?
910                if ((have >= 1) && (!((a == 0) || (a == 1)))) return false; // 'min' for string (single/multi) can be only 0 or 1
911                if ((have >= 2) && (b < -1)) return false; // max=-1 means unlimited, >=0 is max len, max<-1 is not allowed
912        }
913        break;
914        }
915        return true;
916}
917
918SString ParamInterface::friendlyTypeDescrFromTypeDef(const char* type)
919{
920        SString t;
921        switch (type[0])
922        {
923        case 'd': t += "integer";
924        {paInt a, b, c; int n = getMinMaxIntFromTypeDef(type, a, b, c); if ((n >= 2) && (b >= a)) t += SString::sprintf(" %d..%d", a, b); if (n >= 3) t += SString::sprintf(" (default %d)", c); }
925        break;
926        case 'f': t += "float";
927        {double a, b, c; int n = getMinMaxDoubleFromTypeDef(type, a, b, c); if ((n >= 2) && (b >= a)) t += SString::sprintf(" %g..%g", a, b); if (n >= 3) t += SString::sprintf(" (default %g)", c); }
928        break;
929        case 's': t += "string";
930        {int a, b; SString c; int n = getMinMaxStringFromTypeDef(type, a, b, c); if ((n >= 2) && (b > 0)) t += SString::sprintf(", max %d chars", b); if (n >= 3) t += SString::sprintf(" (default \"%s\")", c.c_str()); }
931        break;
932        case 'x': t += "untyped value"; break;
933        case 'p': t += "function"; break;
934        case 'o': t += "object"; if (type[1]) { t += " of class "; t += type + 1; } break;
935        default: return "unknown type";
936        }
937        return t;
938}
939
940//////////////////////////////// PARAM ////////////////////////////////////
941
942#ifdef _DEBUG
943void SimpleAbstractParam::sanityCheck(int i)
944{
945        ParamEntry *pe = entry(i);
946
947        const char* t = pe->type;
948        const char* err = NULL;
949
950        if (!isValidTypeDescription(t))
951                err = "invalid type description";
952        if (*t == 'p')
953        {
954                if (pe->fun1 == NULL)
955                {
956                        MutableParamInterface *mpi = dynamic_cast<MutableParamInterface*>(this);
957                        if (mpi == NULL) // Avoid false positives for script-driven mutable params, like expdefs. This can't be reliably verified. Function pointer checking is meant for static param tables anyway so it's probably not a big deal.
958                                err = "no procedure defined";
959                }
960        }
961        else
962        {
963                if ((t[0] == 'o') && (t[1] == ' '))
964                {
965                        err = "space after 'o'";
966                }
967                if (!(pe->flags & (PARAM_READONLY | PARAM_DONTSAVE | PARAM_USERREADONLY | PARAM_CONST | PARAM_DONTLOAD | PARAM_LINECOMMENT | PARAM_OBJECTSET)))
968                { //write access
969                        if ((pe->fun2 == NULL) && (pe->offset == PARAM_ILLEGAL_OFFSET))
970                                err = "no field defined (GETONLY without PARAM_READONLY?)";
971                }
972        }
973        if (err != NULL)
974                logPrintf("SimpleAbstractParam", "sanityCheck", LOG_ERROR,
975                        "Invalid ParamEntry for %s (%s)", nameDotPropertyForMessages(i).c_str(), err);
976}
977#endif
978
979void *SimpleAbstractParam::getTarget(int i)
980{
981        return (void*)(((char*)object) + entry(i)->offset);
982        //return &(object->*(entry(i)->fldptr));
983}
984
985///////// get
986
987#ifdef _DEBUG
988#define SANITY_CHECK(i) sanityCheck(i)
989#else
990#define SANITY_CHECK(i)
991#endif
992
993paInt SimpleAbstractParam::getInt(int i)
994{
995        SANITY_CHECK(i);
996        ExtValue v;
997        ParamEntry *pe = entry(i);
998        if (pe->fun1)
999        {
1000                (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v);
1001                return v.getInt();
1002        }
1003        else
1004        {
1005                void *target = getTarget(i);
1006                return *((paInt*)target);
1007        }
1008}
1009
1010double SimpleAbstractParam::getDouble(int i)
1011{
1012        SANITY_CHECK(i);
1013        ExtValue v;
1014        ParamEntry *pe = entry(i);
1015        if (pe->fun1)
1016        {
1017                (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v);
1018                return v.getDouble();
1019        }
1020        else
1021        {
1022                void *target = getTarget(i);
1023                return *((double*)target);
1024        }
1025}
1026
1027SString SimpleAbstractParam::getString(int i)
1028{
1029        SANITY_CHECK(i);
1030        ExtValue v;
1031        ParamEntry *pe = entry(i);
1032        if (pe->fun1)
1033        {
1034                (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v);
1035                return v.getString();
1036        }
1037        else
1038        {
1039                void *target = getTarget(i);
1040                return *((SString*)target);
1041        }
1042}
1043
1044ExtObject SimpleAbstractParam::getObject(int i)
1045{
1046        SANITY_CHECK(i);
1047        ExtValue v;
1048        ParamEntry *pe = entry(i);
1049        if (pe->fun1)
1050        {
1051                (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v);
1052                return v.getObject();
1053        }
1054        else
1055        {
1056                void *target = getTarget(i);
1057                return *((ExtObject*)target);
1058        }
1059}
1060
1061ExtValue SimpleAbstractParam::getExtValue(int i)
1062{
1063        SANITY_CHECK(i);
1064        ExtValue v;
1065        ParamEntry *pe = entry(i);
1066        if (pe->fun1)
1067        {
1068                (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v);
1069                return v;
1070        }
1071        else
1072        {
1073                void *target = getTarget(i);
1074                return *((ExtValue*)target);
1075        }
1076}
1077
1078
1079//////// set
1080
1081int SimpleAbstractParam::setInt(int i, paInt x)
1082{
1083        SANITY_CHECK(i);
1084        ExtValue v;
1085        ParamEntry *pe = entry(i);
1086        if (pe->flags & PARAM_READONLY) return PSET_RONLY;
1087        paInt xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below
1088        paInt mn = 0, mx = 0, de = 0;
1089        int result = 0;
1090        if (getMinMaxIntFromTypeDef(pe->type, mn, mx, de) >= 2)
1091                if (mn <= mx) // else if mn>mx then the min/max constraint makes no sense and there is no checking
1092                {
1093                        if (x < mn) { x = mn; result = PSET_HITMIN; }
1094                        else if (x > mx) { x = mx; result = PSET_HITMAX; }
1095                }
1096
1097        if (pe->fun2)
1098        {
1099                v.setInt(x);
1100                result |= (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v);
1101        }
1102        else
1103        {
1104                void *target = getTarget(i);
1105                if (dontcheckchanges || (*((paInt*)target) != x))
1106                {
1107                        result |= PSET_CHANGED;
1108                        *((paInt*)target) = x;
1109                }
1110        }
1111        ::messageOnExceedRange(this, i, result, xcopy);
1112        return result;
1113}
1114
1115int SimpleAbstractParam::setDouble(int i, double x)
1116{
1117        SANITY_CHECK(i);
1118        ExtValue v;
1119        ParamEntry *pe = entry(i);
1120        if (pe->flags & PARAM_READONLY) return PSET_RONLY;
1121        double xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below
1122        double mn = 0, mx = 0, de = 0;
1123        int result = 0;
1124        if (getMinMaxDoubleFromTypeDef(pe->type, mn, mx, de) >= 2)
1125                if (mn <= mx) // else if mn>mx then the min/max constraint makes no sense and there is no checking
1126                {
1127                        if (x < mn) { x = mn; result = PSET_HITMIN; }
1128                        else if (x > mx) { x = mx; result = PSET_HITMAX; }
1129                }
1130
1131        if (pe->fun2)
1132        {
1133                v.setDouble(x);
1134                result |= (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v);
1135        }
1136        else
1137        {
1138                void *target = getTarget(i);
1139                if (dontcheckchanges || (*((double*)target) != x))
1140                {
1141                        result |= PSET_CHANGED;
1142                        *((double*)target) = x;
1143                }
1144        }
1145        ::messageOnExceedRange(this, i, result, xcopy);
1146        return result;
1147}
1148
1149int SimpleAbstractParam::setString(int i, const SString& x)
1150{
1151        SANITY_CHECK(i);
1152        ExtValue v;
1153        SString vs;
1154        const SString *xx = &x;
1155        ParamEntry *pe = entry(i);
1156        if (pe->flags & PARAM_READONLY) return PSET_RONLY;
1157        SString xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below
1158        const char* t = pe->type + 1;
1159        while (*t) if (*t == ' ') break; else t++;
1160        int mn = 0, mx = 0;
1161        int result = 0;
1162        if (sscanf(t, "%d %d", &mn, &mx) == 2) //using getMinMax would also get default value, which is not needed here
1163        {
1164                if ((x.length() > mx) && (mx >= 0))
1165                {
1166                        vs = x.substr(0, mx);
1167                        xx = &vs;
1168                        result |= PSET_HITMAX;
1169                }
1170        }
1171
1172        if (pe->fun2)
1173        {
1174                v.setString(*xx);
1175                result |= (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v);
1176        }
1177        else
1178        {
1179                void *target = getTarget(i);
1180                if (dontcheckchanges || (!(*((SString*)target) == *xx)))
1181                {
1182                        result |= PSET_CHANGED;
1183                        *((SString*)target) = *xx;
1184                }
1185        }
1186        ::messageOnExceedRange(this, i, result, xcopy);
1187        return result;
1188}
1189
1190int SimpleAbstractParam::setObject(int i, const ExtObject& x)
1191{
1192        SANITY_CHECK(i);
1193        ExtValue v;
1194        ParamEntry *pe = entry(i);
1195        if (pe->flags & PARAM_READONLY) return PSET_RONLY;
1196        if (pe->flags & PARAM_OBJECTSET)
1197        {
1198                ExtObject o = getObject(i);
1199                Param tmp;
1200                ParamInterface* oif = o.getParamInterface(tmp);
1201                int ass;
1202                if (oif && ((ass = oif->findId("assign")) >= 0))
1203                {
1204                        ExtValue arg = x;
1205                        oif->call(ass, &arg, &v);
1206                }
1207                else
1208                        logPrintf("SimpleAbstractParam", "setObject", LOG_ERROR,
1209                                "%s is PARAM_OBJECTSET but no 'assign()' in %s", nameDotPropertyForMessages(i).c_str(), o.interfaceName());
1210                return PSET_CHANGED;
1211        }
1212        ExtObject xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below
1213        if (pe->fun2)
1214        {
1215                v.setObject(x);
1216                int result = (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v);
1217                ::messageOnExceedRange(this, i, result, xcopy);
1218                return result;
1219        }
1220        else
1221        {
1222                void *target = getTarget(i);
1223                *((ExtObject*)target) = x;
1224                return PSET_CHANGED;
1225        }
1226}
1227
1228int SimpleAbstractParam::setExtValue(int i, const ExtValue& x)
1229{
1230        SANITY_CHECK(i);
1231        ParamEntry *pe = entry(i);
1232        if (pe->flags & PARAM_READONLY) return PSET_RONLY;
1233        ExtValue xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below
1234        if (pe->fun2)
1235        {
1236                int result = (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &x);
1237                ::messageOnExceedRange(this, i, result, xcopy);
1238                return result;
1239        }
1240        else
1241        {
1242                void *target = getTarget(i);
1243                *((ExtValue*)target) = x;
1244                return PSET_CHANGED;
1245        }
1246}
1247
1248void SimpleAbstractParam::call(int i, ExtValue *args, ExtValue *ret)
1249{
1250        SANITY_CHECK(i);
1251        ParamEntry *pe = entry(i);
1252        if (!pe) return;
1253        if (pe->fun1 && (pe->type[0] == 'p'))
1254                (*(void(*)(void*, ExtValue*, ExtValue*))pe->fun1)(object, args, ret);
1255        else
1256        {
1257                logPrintf("SimpleAbstractParam", "call", LOG_ERROR,
1258                        (*pe->type != 'p') ? "%s is not a function" : "Internal error - undefined function pointer for %s", nameDotPropertyForMessages(i).c_str());
1259                ret->setInvalid();
1260        }
1261}
1262
1263void SimpleAbstractParam::setDefault()
1264{
1265        bool save = dontcheckchanges;
1266        dontcheckchanges = 1;
1267        ParamInterface::setDefault();
1268        dontcheckchanges = save;
1269}
1270
1271void SimpleAbstractParam::setDefault(int i)
1272{
1273        bool save = dontcheckchanges;
1274        dontcheckchanges = 1;
1275        ParamInterface::setDefault(i);
1276        dontcheckchanges = save;
1277}
1278
1279// Returns the address of the beginning of the line.
1280// len = line length (without \n).
1281// 0 may mean the line with length=0 or the end of the SString.
1282// poz is advanced to the beginning of the next line.
1283// A typical loop: for(poz=0;poz<s.d;) {line=getline(s,poz,len);...
1284static const char *getline(const SString &s, int &poz, int &len)
1285{
1286        const char *beg = s.c_str() + poz;
1287        if (poz >= s.length()) { poz = s.length(); len = 0; return s.c_str() + s.length(); }
1288        const char *lf = strchr(beg, '\n');
1289        if (!lf) { lf = s.c_str() + s.length() - 1; poz = s.length(); }
1290        else { poz = (int)(lf - s.c_str()) + 1; if (poz > s.length()) poz = s.length(); }
1291        while (lf >= beg) if ((*lf == '\n') || (*lf == '\r')) lf--; else break;
1292        len = (int)(lf - beg) + 1;
1293        return beg;
1294}
1295
1296int ParamInterface::loadSingleLine(const SString &s, LoadOptions &options)
1297{
1298        int i; // the index number of the parameter
1299        int tmpi;
1300        int len;
1301        int ret;
1302        int fields_loaded = 0;
1303        const char *t, *lin, *end;
1304        const char *equals_sign, *field_end, *next_field;
1305        char remember;
1306        const char *quote, *quote2;
1307        const char *value, *valstop;
1308        SString tmpvalue;
1309        bool parse_failed = false;
1310        if (options.offset >= s.length()) return fields_loaded;
1311        t = s.c_str() + options.offset;
1312
1313        lin = getline(s, options.offset, len); // all fields must be encoded in a single line
1314        if (!len) return fields_loaded; // empty line = end
1315        i = 0;
1316        end = lin + len;
1317        while (t < end)
1318        {
1319                // processing a single field
1320                // "p:name=field_value,  field_name=field_value  , name=value..."
1321                //                     ^ ^-t (after)           ^ ^_next_field
1322                //                     \_t (before)            \_field_end
1323                while (isspace(*t)) if (t < end) t++; else return fields_loaded;
1324
1325                field_end = strchrlimit(t, ',', end); if (!field_end) field_end = end;
1326                next_field = field_end;
1327                while ((field_end > t) && isblank(field_end[-1])) field_end--;
1328                quote = strchrlimit(t, '\"', field_end);
1329                if (quote)
1330                {
1331                        quote2 = skipQuoteString(quote + 1, end);
1332                        if (quote2 > field_end)
1333                        {
1334                                field_end = strchrlimit(quote2 + 1, ',', end);
1335                                if (!field_end) field_end = end;
1336                                next_field = field_end;
1337                        }
1338                        equals_sign = strchrlimit(t, '=', quote);
1339                }
1340                else
1341                {
1342                        equals_sign = strchrlimit(t, '=', field_end);
1343                        quote2 = 0;
1344                }
1345                if (equals_sign == t) { t++; equals_sign = 0; }
1346                if (field_end == t)     // skip empty value
1347                {
1348                        t++; if (i >= 0) i++;
1349                        continue;
1350                }
1351                if (equals_sign) // have parameter name
1352                {
1353                        tmpi = findIdn(t, (int)(equals_sign - t));
1354                        i = tmpi;
1355                        if (tmpi < 0)
1356                        {
1357                                SString name(t, (int)(equals_sign - t));
1358                                logPrintf("Param", "loadSingleLine", LOG_WARN, "Unknown property '%s.%s' (ignored)", getName(), name.c_str());
1359                        }
1360                        t = equals_sign + 1; // t=value
1361                }
1362#ifdef WARN_MISSING_NAME
1363                else // no parameter name
1364                {
1365#ifdef SAVE_SELECTED_NAMES
1366                        if ((i < 0) // field after unknown field
1367                                || (i >= getPropCount()) // field after last field
1368                                || !(flags(i) & PARAM_CANOMITNAME)) // valid field but it can't be skipped
1369#endif
1370                        {
1371                                if (i < getPropCount())
1372                                        logPrintf("Param", "loadSingleLine", LOG_WARN, "Missing property name in '%s'", getName());
1373                                else // 'i' can go past PropCount because of moving to subsequent properties by i++, id(i) is then NULL
1374                                        logPrintf("Param", "loadSingleLine", LOG_WARN, "Value after the last property of '%s'", getName());
1375                        }
1376                }
1377                //else skipping a skippable field
1378#endif
1379                if ((i >= 0) && id(i)) // shared by name and skippped name cases
1380                {
1381                        value = t;
1382                        if (quote)
1383                        {
1384                                tmpvalue.copyFrom(quote + 1, (int)(quote2 - quote) - 1);
1385                                sstringUnquote(tmpvalue);
1386                                value = tmpvalue.c_str();
1387                                valstop = quote2;
1388                        }
1389                        else
1390                                if (field_end < end) valstop = field_end; else valstop = end;
1391
1392                        remember = *valstop;
1393                        *(char*)valstop = 0;
1394                        ret = setFromString(i, value, true);
1395                        fields_loaded++;
1396                        if (ret & PSET_PARSEFAILED)
1397                                parse_failed = true;
1398                        *(char*)valstop = remember;
1399                }
1400
1401                if (i >= 0) i++;
1402#ifdef __CODEGUARD__
1403                if (next_field < end - 1) t = next_field + 1; else return fields_loaded;
1404#else
1405                t = next_field + 1;
1406#endif
1407        }
1408        if (parse_failed) options.parse_failed = true;
1409        return fields_loaded;
1410}
1411
1412int Param::grmember(int g, int a)
1413{
1414        if ((getGroupCount() < 2) && (!g))
1415                return (a < getPropCount()) ? a : -9999;
1416
1417        ParamEntry *e = entry(0);
1418        int x = 0, i = 0;
1419        for (; e->id; i++, e++)
1420        {
1421                if (e->group == g)
1422                        if (a == x) return i; else x++;
1423        }
1424        return -9999;
1425}
Note: See TracBrowser for help on using the repository browser.