// This file is a part of the Framsticks GDK. // Copyright (C) 1999-2014 Maciej Komosinski and Szymon Ulatowski. See LICENSE.txt for details. // Refer to http://www.framsticks.com/ for further information. #include #include #include "param.h" #include #include "common/framsg.h" #include //#define SAVE_ALL_NAMES #define SAVE_SELECTED_NAMES #define WARN_MISSING_NAME char MakeCodeGuardHappy; ParamEntry empty_paramtab[] = { { "Empty", 1, 0, "Empty", }, { 0, 0, 0, }, }; static void czytdotyldy(VirtFILE *f, SString &s) { SString temp; int z; char last_char = 0; while ((z = fgetc(f)) != EOF) { if (z == '~') if (last_char != '\\') break; last_char = (char)z; temp += last_char; } s = temp; } static const char *strchrlimit(const char *t, int ch, const char *limit) { int n = (int)(limit - t); for (; (n > 0) && *t; t++, n--) if (*t == ch) return t; return 0; } void ParamInterface::copyFrom(ParamInterface *src) { int n = getPropCount(); ExtValue v; int j; for (int i = 0; i < n; i++) if ((!(flags(i)&PARAM_READONLY)) && (*type(i) != 'p')) { j = src->findId(id(i)); if (j < 0) continue; src->get(j, v); set(i, v); } } void ParamInterface::quickCopyFrom(ParamInterface *src) { int n = getPropCount(); ExtValue v; for (int i = 0; i < n; i++) if ((!(flags(i)&PARAM_READONLY)) && (*type(i) != 'p')) { src->get(i, v); set(i, v); } } int ParamInterface::getMinMax(int prop, paInt& minumum, paInt& maximum, paInt &def) { const char* t = type(prop) + 1; while (*t) if (*t == ' ') break; else t++; return sscanf(t, PA_INT_SCANF " " PA_INT_SCANF " " PA_INT_SCANF, &minumum, &maximum, &def); } int ParamInterface::getMinMax(int prop, double& minumum, double& maximum, double& def) { const char* t = type(prop) + 1; while (*t) if (*t == ' ') break; else t++; return sscanf(t, "%lg %lg %lg", &minumum, &maximum, &def); } void ParamInterface::setDefault(bool numericonly) { int n = getPropCount(); for (int i = 0; i < n; i++) setDefault(i, numericonly); } void ParamInterface::setMin() { int n = getPropCount(); for (int i = 0; i < n; i++) setMin(i); } void ParamInterface::setMax() { int n = getPropCount(); for (int i = 0; i < n; i++) setMax(i); } void ParamInterface::setDefault(int i, bool numericonly) { const char *t = type(i); switch (*t) { case 'f': { double a = 0, b = 0, c = 0; if (getMinMax(i, a, b, c) < 3) c = a; setDouble(i, c); } break; case 'd': { paInt a = 0, b = 0, c = 0; if (getMinMax(i, a, b, c) < 3) c = a; setInt(i, c); } break; default: if (!numericonly) set(i, ""); } } void ParamInterface::setMin(int i) { const char *t = type(i); switch (*t) { case 'f': { double a = 0, b = 0, c = 0; getMinMax(i, a, b, c); setDouble(i, a); } break; case 'd': { paInt a = 0, b = 0, c = 0; getMinMax(i, a, b, c); setInt(i, a); } break; default: set(i, ""); } } void ParamInterface::setMax(int i) { const char *t = type(i); switch (*t) { case 'f': { double a = 0, b = 0, c = 0; getMinMax(i, a, b, c); setDouble(i, b); } break; case 'd': { paInt a = 0, b = 0, c = 0; getMinMax(i, a, b, c); setInt(i, b); } break; default: set(i, ""); } } SString ParamInterface::getStringById(const char*prop) {int i=findId(prop); if (i>=0) return getString(i); else return SString();} paInt ParamInterface::getIntById(const char*prop) {int i=findId(prop); if (i>=0) return getInt(i); else return 0;} double ParamInterface::getDoubleById(const char*prop) {int i=findId(prop); if (i>=0) return getDouble(i); else return 0;} ExtObject ParamInterface::getObjectById(const char*prop) {int i=findId(prop); if (i>=0) return getObject(i); else return ExtObject();} ExtValue ParamInterface::getExtValueById(const char*prop) {int i=findId(prop); if (i>=0) return getExtValue(i); else return ExtValue();} int ParamInterface::setIntById(const char* prop,paInt v) {int i=findId(prop); if (i>=0) return setInt(i,v); else return PSET_NOPROPERTY;} int ParamInterface::setDoubleById(const char* prop,double v) {int i=findId(prop); if (i>=0) return setDouble(i,v); else return PSET_NOPROPERTY;} int ParamInterface::setStringById(const char* prop,const SString &v) {int i=findId(prop); if (i>=0) return setString(i,v); else return PSET_NOPROPERTY;} int ParamInterface::setObjectById(const char* prop,const ExtObject &v) {int i=findId(prop); if (i>=0) return setObject(i,v); else return PSET_NOPROPERTY;} int ParamInterface::setExtValueById(const char* prop,const ExtValue &v) {int i=findId(prop); if (i>=0) return setExtValue(i,v); else return PSET_NOPROPERTY;} int ParamInterface::setById(const char* prop,const ExtValue &v) {int i=findId(prop); if (i>=0) return set(i,v); else return PSET_NOPROPERTY;} int ParamInterface::save(VirtFILE* f, const char* altname, bool force) { const char *p; SString ws; int err = 0, i; bool withname = false; if ((altname == NULL) || (altname[0] != 0)) { err |= (fputs(altname ? altname : getName(), f) == EOF); err |= (fputs(":\n", f) == EOF); withname = true; } for (i = 0; p = id(i); i++) err |= saveprop(f, i, p, force); if (withname) err |= (fputs("\n", f) == EOF); return err; } const char* ParamInterface::SERIALIZATION_PREFIX = "@Serialized:"; int ParamInterface::saveprop(VirtFILE* f, int i, const char* p, bool force) { if ((flags(i)&PARAM_DONTSAVE) && (!force)) return 0; const char *typ = type(i); if ((*typ == 'p') || (*typ == 'o')) return 0; const char *t, *w; SString ws; int err = 0, cr; err |= (fputs(p, f) == EOF); fputc(':', f); cr = 0; if (*typ == 'x') { ExtValue ex; get(i, ex); ws = SString(SERIALIZATION_PREFIX) + ex.serialize(); } else ws = get(i); quoteTilde(ws); w = ws; if (ws.len() > 50) cr = 1; else for (t = w; *t; t++) if ((*t == 10) || (*t == 13)) { cr = 1; break; } if (cr) fputs("~\n", f); err |= (fputs(w, f) == EOF); err |= (fputs(cr ? "~\n" : "\n", f) == EOF); return err; } int SimpleAbstractParam::isequal(int i, void* defdata) { // defdata->member == object->member ? void *backup = object; switch (type(i)[0]) { case 'd': { select(defdata); paInt x = getInt(i); select(backup); return x == getInt(i); } case 'f': { select(defdata); double x = getDouble(i); select(backup); return x == getDouble(i); } case 's': { select(defdata); SString x = getString(i); select(backup); return x == getString(i); } } return 1; } void SimpleAbstractParam::save2(SString& f, void *defdata, bool addcr, bool all_names) { // defdata!=NULL -> does not save default values const char *p; int i; int needlabel = 0; int first = 1; SString val; SString t; int fl; // t+=SString(getName()); t+=':'; for (i = 0; p = id(i); i++) if (!((fl = flags(i))&PARAM_DONTSAVE)) { if (defdata && isequal(i, defdata)) needlabel = 1; else { if (!first) t += ", "; #ifndef SAVE_ALL_NAMES #ifdef SAVE_SELECTED_NAMES if (needlabel || all_names || !(fl & PARAM_CANOMITNAME)) #else if (needlabel) #endif #endif { t += p; t += "="; needlabel = 0; } if (type(i)[0] == 's') { // string - special case SString str = getString(i); if (strContainsOneOf(str, ", \\\n\r\t\"")) { t += "\""; sstringQuote(str); t += str; t += "\""; } else t += str; } else t += get(i); first = 0; } } if (addcr) t += "\n"; f += t; } int ParamInterface::load(VirtFILE* f) { SString buf; int i; const char *p, *p0; int p_len; bool loaded; int fields_loaded = 0; while (loadSStringLine(f, buf)) { const char* t = (const char*)buf; p0 = t; while ((*p0 == ' ') || (*p0 == '\t')) p0++; if (!*p0) break; p = strchr(p0, ':'); if (!p) continue; p_len = (int)(p - p0); loaded = false; if (p_len && ((i = findIdn(p0, p_len)) >= 0) && (!(flags(i)&PARAM_DONTLOAD))) { if (p0[p_len + 1] == '~') { SString s; czytdotyldy(f, s); removeCR(s); int ch; while ((ch = fgetc(f)) != EOF) if (ch == '\n') break; unquoteTilde(s); set(i, (const char*)s); } else { set(i, p0 + p_len + 1); } fields_loaded++; loaded = true; } if ((!loaded) && (p0[p_len + 1] == '~')) { // eat unrecognized multiline field SString s; czytdotyldy(f, s); int ch; while ((ch = fgetc(f)) != EOF) if (ch == '\n') break; } } return fields_loaded; } /* SString SimpleAbstractParam::getString(int i) { char *t; switch (*(t=type(i))) { case 'd': { for (i=atol(get(i));i>=0;i--) if (t) t=strchr(t+1,'~'); if (t) { t++; char *t2=strchr(t,'~'); if (!t2) t2=t+strlen(t); SString str; strncpy(str.directWrite(t2-t),t,t2-t); str.endWrite(t2-t); return str; } } } return get(i); } */ int ParamInterface::findId(const char* n) { int i; const char *p; for (i = 0; p = id(i); i++) if (!strcmp(n, p)) return i; return -1; } int ParamInterface::findIdn(const char* naz, int n) { int i; const char *p; for (i = 0; p = id(i); i++) if ((!strncmp(naz, p, n)) && (!p[n])) return i; return -1; } void ParamInterface::get(int i, ExtValue &ret) { switch (type(i)[0]) { case 'd': ret.setInt(getInt(i)); break; case 'f': ret.setDouble(getDouble(i)); break; case 's': ret.setString(getString(i)); break; case 'o': ret.setObject(getObject(i)); break; case 'x': ret = getExtValue(i); break; default: FMprintf("ParamInterface", "get", FMLV_ERROR, "'%s.%s' is not a field", getName(), id(i)); } } static bool stringIsNumeric(const char* str) {// /-?.?[0-9]+/ if (!str) return false; if (*str == '-') str++; if (*str == '.') str++; return isdigit(*str) != 0; } int ParamInterface::setInt(int i, const char* str) { if (!stringIsNumeric(str)) { paInt a, b, c; if (getMinMax(i, a, b, c) >= 3) return setInt(i, c); else return setInt(i, (paInt)0); } else return setInt(i, ExtValue::getInt(str)); } int ParamInterface::setDouble(int i, const char* str) { if (!stringIsNumeric(str)) { double a, b, c; if (getMinMax(i, a, b, c) >= 3) return setDouble(i, c); else return setDouble(i, (double)0); } else return setDouble(i, ExtValue::getDouble(str)); } int ParamInterface::set(int i, const ExtValue &v) { switch (type(i)[0]) { case 'd': if ((v.type == TInt) || (v.type == TDouble)) return setInt(i, v.getInt()); else { if (v.type == TObj) FMprintf("ParamInterface", "set", FMLV_WARN, "Getting integer value from object reference (%s)", (const char*)v.getString()); return setInt(i, (const char*)v.getString()); } case 'f': if ((v.type == TInt) || (v.type == TDouble)) return setDouble(i, v.getDouble()); else { if (v.type == TObj) FMprintf("ParamInterface", "set", FMLV_WARN, "Getting floating point value from object reference (%s)", (const char*)v.getString()); return setDouble(i, (const char*)v.getString()); } case 's': { SString t = v.getString(); return setString(i, t); } case 'o': return setObject(i, v.getObject()); case 'x': return setExtValue(i, v); default: FMprintf("ParamInterface", "set", FMLV_ERROR, "'%s.%s' is not a field", getName(), id(i)); } return 0; } int ParamInterface::set(int i, const char *v) { switch (type(i)[0]) { case 'd': return setInt(i, v); case 'f': return setDouble(i, v); case 's': { SString t(v); return setString(i, t); } case 'x': { ExtValue e; const char* after; if (!strncmp(v, SERIALIZATION_PREFIX, strlen(SERIALIZATION_PREFIX))) { after = e.deserialize(v + strlen(SERIALIZATION_PREFIX)); if ((after == NULL) || (*after)) FMprintf("ParamInterface", "set", FMLV_WARN, "serialization format mismatch in %s.%s", (getName() ? getName() : ""), id(i)); } else if ((after = e.parseNumber(v)) && (*after == 0)) //consumed the whole string { //OK! } else { e.setString(SString(v)); } return setExtValue(i, e); } } return 0; } SString ParamInterface::getText(int i) { const char *t; if ((*(t = type(i))) == 'd') { for (int j = getInt(i); j >= 0; j--) if (t) t = strchr(t + 1, '~'); if (t) { t++; const char *t2 = strchr(t, '~'); if (!t2) t2 = t + strlen(t); return SString(t, (int)(t2 - t)); } } return get(i); } SString ParamInterface::get(int i) { switch (type(i)[0]) { case 'd': return SString::valueOf(getInt(i)); case 'f': return SString::valueOf(getDouble(i)); case 's': return getString(i); } ExtValue v; get(i, v); return v.getString(); } //////////////////////////////// PARAM //////////////////////////////////// #ifdef DEBUG void SimpleAbstractParam::sanityCheck(int i) { ParamEntry *pe=entry(i); const char* t=pe->type; const char* err=NULL; if (*t=='p') { if (pe->fun1==NULL) err="no procedure defined"; } else { if (!(pe->flags & PARAM_READONLY)) { //write access if ((pe->fun2==NULL)&&(pe->offset==PARAM_ILLEGAL_OFFSET)) err="no field defined (GETONLY without PARAM_READONLY?)"; } } if (err!=NULL) FMprintf("SimpleAbstractParam","sanityCheck", FMLV_ERROR, "Invalid ParamEntry for %s.%s (%s)", getName(), pe->id, err); } #endif void *SimpleAbstractParam::getTarget(int i) { return (void*)(((char*)object) + entry(i)->offset); //return &(object->*(entry(i)->fldptr)); } ///////// get #ifdef DEBUG #define SANITY_CHECK(i) sanityCheck(i) #else #define SANITY_CHECK(i) #endif paInt SimpleAbstractParam::getInt(int i) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->fun1) { (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v); return v.getInt(); } else { void *target = getTarget(i); return *((paInt*)target); } } double SimpleAbstractParam::getDouble(int i) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->fun1) { (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v); return v.getDouble(); } else { void *target = getTarget(i); return *((double*)target); } } SString SimpleAbstractParam::getString(int i) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->fun1) { (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v); return v.getString(); } else { void *target = getTarget(i); return *((SString*)target); } } ExtObject SimpleAbstractParam::getObject(int i) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->fun1) { (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v); return v.getObject(); } else { void *target = getTarget(i); return *((ExtObject*)target); } } ExtValue SimpleAbstractParam::getExtValue(int i) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->fun1) { (*(void(*)(void*, ExtValue*))pe->fun1)(object, &v); return v; } else { void *target = getTarget(i); return *((ExtValue*)target); } } //////// set int SimpleAbstractParam::setInt(int i, paInt x) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->flags&PARAM_READONLY) return PSET_RONLY; paInt xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below paInt a = 0, b = 0; int result = 0; const char* t = pe->type + 1; while (*t) if (*t == ' ') break; else t++; if (sscanf(t, PA_INT_SCANF " " PA_INT_SCANF, &a, &b) == 2) if (a <= b) // if maxb) { x = b; result = PSET_HITMAX; } } if (pe->fun2) { v.setInt(x); result |= (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v); } else { void *target = getTarget(i); if (dontcheckchanges || (*((paInt*)target) != x)) { result |= PSET_CHANGED; *((paInt*)target) = x; } } messageOnExceedRange(i, result, xcopy); return result; } int SimpleAbstractParam::setDouble(int i, double x) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->flags&PARAM_READONLY) return PSET_RONLY; double xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below double a = 0, b = 0; int result = 0; const char* t = pe->type + 1; while (*t) if (*t == ' ') break; else t++; if (sscanf(t, "%lg %lg", &a, &b) == 2) if (a <= b) // if maxb) { x = b; result = PSET_HITMAX; } } if (pe->fun2) { v.setDouble(x); result |= (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v); } else { void *target = getTarget(i); if (dontcheckchanges || (*((double*)target) != x)) { result |= PSET_CHANGED; *((double*)target) = x; } } messageOnExceedRange(i, result, xcopy); return result; } int SimpleAbstractParam::setString(int i, const SString& x) { SANITY_CHECK(i); ExtValue v; SString vs; const SString *xx = &x; ParamEntry *pe = entry(i); if (pe->flags&PARAM_READONLY) return PSET_RONLY; SString xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below const char* t = pe->type + 1; while (*t) if (*t == ' ') break; else t++; paInt a = 0, b = 0; int result = 0; if (sscanf(t, PA_INT_SCANF " " PA_INT_SCANF, &a, &b) == 2) { if ((x.len() > b) && (b > 0)) { vs = x.substr(0, b); xx = &vs; result |= PSET_HITMAX; } } if (pe->fun2) { v.setString(*xx); result |= (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v); } else { void *target = getTarget(i); if (dontcheckchanges || (!(*((SString*)target) == *xx))) { result |= PSET_CHANGED; *((SString*)target) = x; } } messageOnExceedRange(i, result, xcopy); return result; } int SimpleAbstractParam::setObject(int i, const ExtObject& x) { SANITY_CHECK(i); ExtValue v; ParamEntry *pe = entry(i); if (pe->flags&PARAM_READONLY) return PSET_RONLY; ExtObject xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below if (pe->fun2) { v.setObject(x); int result = (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &v); messageOnExceedRange(i, result, xcopy); return result; } else { void *target = getTarget(i); *((ExtObject*)target) = x; return PSET_CHANGED; } } int SimpleAbstractParam::setExtValue(int i, const ExtValue& x) { SANITY_CHECK(i); ParamEntry *pe = entry(i); if (pe->flags&PARAM_READONLY) return PSET_RONLY; ExtValue xcopy = x; //only needed for messageOnExceedRange(): retain original, requested value of x because it may be changed below if (pe->fun2) { int result = (*(int(*)(void*, const ExtValue*))pe->fun2)(object, &x); messageOnExceedRange(i, result, xcopy); return result; } else { void *target = getTarget(i); *((ExtValue*)target) = x; return PSET_CHANGED; } } void SimpleAbstractParam::call(int i, ExtValue *args, ExtValue *ret) { SANITY_CHECK(i); ParamEntry *pe = entry(i); if (!pe) return; if (pe->fun1 && (pe->type[0] == 'p')) (*(void(*)(void*, ExtValue*, ExtValue*))pe->fun1)(object, args, ret); else { FMprintf("SimpleAbstractParam", "call", FMLV_ERROR, (*pe->type != 'p') ? "'%s.%s' is not a function" : "Internal error - undefined function pointer for '%s.%s'", getName(), pe->id); } } void SimpleAbstractParam::setDefault(bool numericonly) { bool save = dontcheckchanges; dontcheckchanges = 1; ParamInterface::setDefault(numericonly); dontcheckchanges = save; } void SimpleAbstractParam::setDefault(int i, bool numericonly) { bool save = dontcheckchanges; dontcheckchanges = 1; ParamInterface::setDefault(i, numericonly); dontcheckchanges = save; } // Returns the address of the beginning of the line. // len = line length (without \n). // 0 may mean the line with length=0 or the end of the SString. // poz is advanced to the beginning of the next line. // A typical loop: for(poz=0;poz= s.len()) { poz = s.len(); len = 0; return (const char*)s + s.len(); } const char *lf = strchr(beg, '\n'); if (!lf) { lf = (const char*)s + s.len() - 1; poz = s.len(); } else { poz = (int)(lf - (const char*)s) + 1; if (poz > s.len()) poz = s.len(); } while (lf >= beg) if ((*lf == '\n') || (*lf == '\r')) lf--; else break; len = (int)(lf - beg) + 1; return beg; } int ParamInterface::load2(const SString &s, int &poz) { int i; // the index number of the parameter int tmpi; int len; int ret; int fields_loaded = 0; const char *t, *lin, *end; const char *equals_sign, *comma_sign; char remember; const char *quote, *quote2; const char *value, *valstop; SString tmpvalue; if (poz >= s.len()) return fields_loaded; t = (const char*)s + poz; lin = getline(s, poz, len); // all fields must be encoded in a single line if (!len) return fields_loaded; // empty line = end i = 0; end = lin + len; while (t < end) { // processing a single field while (strchr(" \n\r\t", *t)) if (tcomma_sign) { comma_sign = strchrlimit(quote2 + 1, ',', end); if (!comma_sign) comma_sign = end; } equals_sign = strchrlimit(t, '=', quote); } else { equals_sign = strchrlimit(t, '=', comma_sign); quote2 = 0; } if (equals_sign == t) { t++; equals_sign = 0; } if (comma_sign == t) // skip empty value { t++; i++; continue; } if (equals_sign) // have parameter name { tmpi = findIdn(t, (int)(equals_sign - t)); i = tmpi; if (tmpi < 0) FMprintf("Param", "load2", FMLV_WARN, "Unknown property name for '%s' (ignored)", getName()); t = equals_sign + 1; // t=value } #ifdef WARN_MISSING_NAME else #ifdef SAVE_SELECTED_NAMES if (!(flags(i)&PARAM_CANOMITNAME)) #endif { FMprintf("Param", "load2", FMLV_WARN, "Missing property name in '%s' (assuming '%s')", getName(), id(i) ? id(i) : "unknown property?"); } #endif if ((i >= 0) && id(i)) { value = t; if (quote) { tmpvalue.copyFrom(quote + 1, (int)(quote2 - quote) - 1); sstringUnquote(tmpvalue); value = tmpvalue; valstop = quote2; } else if (comma_sign < end) valstop = comma_sign; else valstop = end; remember = *valstop; *(char*)valstop = 0; ret = set(i, value); fields_loaded++; if (ret&(PSET_HITMAX | PSET_HITMIN)) FMprintf("Param", "load2", FMLV_WARN, "Adjusted '%s' in '%s' (was too %s)", id(i), getName(), (ret&PSET_HITMAX) ? "big" : "small"); *(char*)valstop = remember; } if (i >= 0) i++; #ifdef __CODEGUARD__ if (comma_signid; i++, e++) { if (e->group == g) if (a == x) return i; else x++; } return -9999; }