diff --git a/doc/teapot.lyx b/doc/teapot.lyx index d26cd0f..8c8c726 100644 --- a/doc/teapot.lyx +++ b/doc/teapot.lyx @@ -4625,7 +4625,7 @@ save-tbl file \series default \emph default - + \end_layout \begin_layout Description @@ -4642,7 +4642,7 @@ save-csv file \series default \emph default - + \end_layout \begin_layout Description @@ -4659,7 +4659,7 @@ save-latex file \series default \emph default - + \end_layout \begin_layout Description @@ -4676,7 +4676,7 @@ save-context file \series default \emph default - + \end_layout \begin_layout Description @@ -4855,6 +4855,11 @@ Location Cell labels and the &() \family default function have this type, but there are no location constant literals. + However, +\family typewriter +&(3,2,1), +\family default + for example, acts very much like a location constant literal. \end_layout \begin_layout Description @@ -5041,7 +5046,7 @@ y x \family typewriter \emph default -= +~= \family default \emph on y @@ -5489,7 +5494,8 @@ y z \emph default is omitted, the coordinate of the cell is used. - The second form is in fact a no-op, but it is allowed for convenience + The second form is in fact a no-op, but it is allowed for convenience and + consistency of expressions. \end_layout \begin_layout Description @@ -5515,8 +5521,21 @@ relative. \begin_inset Quotes erd \end_inset - Thus R(-1) returns the value of the cell immediately to the left of this - one. + Thus +\family sans +\series bold +R +\series default +(-1) +\family default + returns the value of the cell immediately to the left of this one, and +\family sans +\series bold + R +\series default +(,,1) +\family default + returns the same cell as this one but on the following layer. \end_layout \begin_layout Description @@ -5541,8 +5560,181 @@ displaced (by). \begin_inset Quotes erd \end_inset - Thus, D(-1) returns the location of the cell immediately to the left of - this one. + Thus, +\family sans +\series bold +D +\series default +(-1) +\family default + returns the location of the cell immediately to the left of this one, and +\family sans +\series bold + D +\series default +(,2,1) +\family default + returns the location of the cell two below this one on the following layer. +\end_layout + +\begin_layout Description +X( +\series medium +label +\emph on + +\begin_inset space ~ +\end_inset + +to, +\begin_inset space ~ +\end_inset + + +\emph default +label +\emph on + +\begin_inset space ~ +\end_inset + +from, +\begin_inset space ~ +\end_inset + + +\emph default +[bool +\emph on + +\begin_inset space ~ +\end_inset + +fix_x +\emph default +] +\emph on +, +\begin_inset space ~ +\end_inset + + +\emph default +[bool +\emph on + +\begin_inset space ~ +\end_inset + +fix_y +\emph default +] +\emph on +, +\begin_inset space ~ +\end_inset + + +\emph default +[bool +\emph on + +\begin_inset space ~ +\end_inset + +fix_z +\emph default +] +\series default +) +\begin_inset Quotes eld +\end_inset + +Excel reference +\begin_inset Quotes erd +\end_inset + +: returns the value of the cell at a computed target location. + This target location is the one reached from the current cell via the same + offset as the cell with label +\emph on +to +\emph default + has from label +\emph on +from. + +\emph default + The idea is that if you label the source of data you want to reference, + say with +\family sans +SRC +\family default +, and the location of some place you want to start referring to it with + +\family sans +REF +\family default +, then you can use +\family sans +\series bold +X +\series default +(SRC,REF) +\family default + to refer to the source data, and fill this formula to neighboring cells + to refer to the neighbors of the source, and it will all continue to work + if either the source or the reference is moved around in the sheet. + +\begin_inset Newline newline +\end_inset + +If the +\emph on +fix_DIM +\emph default + argument is present and positive, then the corresponding coordinate of + the target cell is set to match that of +\emph on +to +\emph default +. + This corresponds to fixing the row or column (or layer) of the reference, + as with a +\begin_inset Quotes eld +\end_inset + +$ +\begin_inset Quotes erd +\end_inset + + character in Excel. + Thus +\family sans +\series bold +X +\series default +(SRC,REF,1,1,1) +\family default + is identical to +\family sans +@(SRC) +\family default +, but you should certainly prefer the latter for clarity of expression. +\begin_inset Newline newline +\end_inset + +There is a corresponding location function +\family sans +X&() +\family default + as well, which takes exactly the same arguments with the same meanings, + but it is rarely needed. + It is provided for completeness. +\begin_inset Newline newline +\end_inset + +See the FAQ below for further discussion of cell references. \end_layout \begin_layout Description @@ -5904,7 +6096,7 @@ clock condition \emph default -,[location[,location]) +,[location[,location]) \series default conditionally clocks the specified cell if the condition is not 0. If two locations are given, all cells in that range will be clocked. @@ -7264,8 +7456,22 @@ z position of the given location, of the currently updated cell if none is given. These functions are usually used in combination with the @ function for - relative relations to other cells. + relative relations to other cell, but see also the convenience functions +\family sans +\series bold +R +\series default +() +\family default + and +\family sans +\series bold +D +\series default +() +\family default + for relative references. \end_layout \begin_layout Subsection @@ -7763,11 +7969,13 @@ X \end_layout \begin_layout Quote -eval(&((@(X)>=0)+x(BAD),y(BAD),z(BAD))) +eval(BAD + &((@(X)>=0),0,0)) \end_layout \begin_layout Standard -The cell labelled +Note this is making use of the fact that you can add locations in the natural + way. + The cell labelled \family typewriter BAD \family default @@ -7786,5 +7994,190 @@ BAD \end_layout +\begin_layout Subsection +But my references don't do the right thing when I move or copy them! +\end_layout + +\begin_layout Standard +If you are used to other spreadsheets, you have probably noticed that references + like +\family sans +@(0,1,0) +\family default + (for the start of the second row) or +\family sans +@(MYDATA) +\family default +(for a location) are +\begin_inset Quotes eld +\end_inset + +absolute +\begin_inset Quotes erd +\end_inset + + – they always refer to the same location no matter where in the spreadsheet + they occur. + And of course sometimes it is convenient, for example when making a column + of consecutive numbers, to refer to nearby cells in a relative manner, + either with say +\family sans +@(x(),y()-1,z())+1 +\family default + or +\family sans +@(&()+&(0,-1,0))+1 +\family default + or just +\family sans +R(,-1)+1 +\family default + to add one to the value of the cell just above. + Then you can fill that expression down and get your column of consecutive + numbers. +\end_layout + +\begin_layout Standard +But these sorts of relative expressions only keep working if the cells move + together with the cells they refer to. + If for example you have a row of cells that are all referring to the row + above with a relative reference (like +\family sans +R(,-1) +\family default +) and you insert another row in between them, your references will be all + messed up. + There is value to +\begin_inset Quotes eld +\end_inset + +Excel-style +\begin_inset Quotes erd +\end_inset + + references that can be used to fill and which also can move around while + still just +\begin_inset Quotes eld +\end_inset + +referring to what you want. +\begin_inset Quotes erd +\end_inset + + +\end_layout + +\begin_layout Standard +To provide for this need, teapot has a function +\family sans +X(SRC, REF) +\family default + to retrieve the value of the cell labeled +\family sans +SRC +\family default + +\bar under +from +\bar default + the cell labeled +\family sans +REF +\family default +. + If the so-labeled cells move around (either the source or the reference) + it will still work. + This is not particularly useful in and of itself; what makes it useful + is that from a cell other than +\family sans +REF +\family default +, it gives you the value of the cell that stands in the same relation to + +\family sans +SRC +\family default + as that cell stands to +\family sans +REF +\family default +. + So in the cell to the right of +\family sans +REF +\family default +, it will give you the value of the cell to the right of +\family sans +SRC +\family default +; in the cell below, it gives you the cell below +\family sans +SRC +\family default +, etc. + Now you can fill a block of cells around +\family sans +REF +\family default + with formulas contaning +\family sans +X(SRC,REF) +\family default + and they will refer to the analogous block of cells around +\family sans +SRC +\family default +. + +\end_layout + +\begin_layout Standard +Sometimes you want to make this kind of reference but fix one of the coordinates + but not the others; +\family sans +X() +\family default + has optional flags for this, as well, so that +\family sans +X(SRC,REF,,,1) +\family default + will always be on the same layer as +\family sans +SRC +\family default + regardless of what layer it is called from or what layer +\family sans +REF +\family default + is on. + Thus +\family sans +X(SRC,REF,1,1,1) +\family default + is just +\family sans +@(SRC) +\family default +, but the intent of the latter is much clearer. +\end_layout + +\begin_layout Standard +You might ask as a follow-up question: Isnt +\family sans +X(SRC, REF) +\family default + much more cumbersome than just referring to cells by coordinate and then + letting Excel just do the right thing as you copy and move either that + formula or the referred-to data? The response to this is that in a typical + spreadsheet, there are only a small number of fundamental references, and + all other references derive from them in this way. + So you generally only need a few labels, and by taking just a little extra + time to apply those labels and refer to them in initial formulas, you are + making the semantics of your references much clearer and in essence documenting + them within your spreadsheet. + This extra effort will therefore be repaid in an easier-to-use, easier-to-under +stand, and easier-to-maintain and update spreadsheet. +\end_layout + \end_body \end_document diff --git a/src/common/eval.c b/src/common/eval.c index 6799330..86f48be 100644 --- a/src/common/eval.c +++ b/src/common/eval.c @@ -18,6 +18,7 @@ #include <math.h> #include <stdio.h> #include <stdlib.h> +extern char *strdup(const char* s); #include <string.h> @@ -44,26 +45,40 @@ Token tcopy(Token n) return result; } /*}}}*/ + /* tfree -- free dynamic data of token */ /*{{{*/ void tfree(Token *n) { - if (n->type==STRING) + Token fake; + fake.type = INT; + tfree_protected(n, fake); +} +/*}}}*/ + +/* tfree_protected -- free dynamic data of token but not if same as protected */ /*{{{*/ +void tfree_protected(Token *n, const Token dontfree) +{ + if (n->type == STRING && + (dontfree.type != STRING || n->u.string != dontfree.u.string)) { free(n->u.string); n->u.string=(char*)0; } - else if (n->type==EEK) + else if (n->type == EEK && + (dontfree.type != EEK || n->u.err != dontfree.u.err)) { free(n->u.err); n->u.err=(char*)0; } - else if (n->type==LIDENT) + else if (n->type==LIDENT && + (dontfree.type != LIDENT || n->u.lident != dontfree.u.lident)) { free(n->u.lident); n->u.lident=(char*)0; } } /*}}}*/ + /* tvecfreetoks -- free the tokens in vector of pointer to tokens */ /*{{{*/ void tvecfreetoks(Token **tvec) { @@ -177,7 +192,7 @@ Token tadd(Token l, Token r) { result.type=EEK; len = strlen(_("wrong types for + operator")); - buf = malloc(len + 128); + buf = malloc(len + 5 + 2*MAX_TYPE_NAME_LENGTH + 1); strcpy(buf, _("wrong types for + operator")); snprintf(buf + len, 128, ": %s + %s", Type_Name[l.type], Type_Name[r.type]); result.u.err = buf; @@ -706,6 +721,17 @@ Token tfuncall(Token *ident, int argc, Token argv[]) return tfunc[ident->u.fident].func(argc, argv); } /*}}}*/ + +static Token relational_type_mismatch(Type l, Type r) +{ + Token mismatch; + mismatch.type = EEK; + char *templ = _("Type mismatch: cannot compare %s and %s"); + mismatch.u.err = malloc(strlen(templ) + 2*MAX_TYPE_NAME_LENGTH + 1); + sprintf(mismatch.u.err, templ, Type_Name[l], Type_Name[r]); + return mismatch; +} + /* tlt -- < operator */ /*{{{*/ Token tlt(Token l, Token r) { @@ -787,12 +813,7 @@ Token tlt(Token l, Token r) if (len < 3) result.u.integer = 0; } /*}}}*/ - else /* return < type error */ /*{{{*/ - { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); - } - /*}}}*/ + else return relational_type_mismatch(l.type, r.type); return result; } /*}}}*/ @@ -881,12 +902,7 @@ Token tle(Token l, Token r) if (len < 3) result.u.integer = 0; } /*}}}*/ - else /* result is <= type error */ /*{{{*/ - { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); - } - /*}}}*/ + else return relational_type_mismatch(l.type, r.type); return result; } /*}}}*/ @@ -969,12 +985,7 @@ Token tge(Token l, Token r) if (len < 3) result.u.integer = 0; } /*}}}*/ - else /* return >= type error */ /*{{{*/ - { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); - } - /*}}}*/ + else return relational_type_mismatch(l.type, r.type); return result; } /*}}}*/ @@ -1058,12 +1069,7 @@ Token tgt(Token l, Token r) else if (r.u.location[len] < l.u.location[len]) ++result.u.integer; if (len < 3) result.u.integer = 0; } - /*}}}*/ else /* result is relation op type error */ /*{{{*/ - { - result.type=EEK; - result.u.err=mystrmalloc(_("type mismatch for relational operator")); - } - /*}}}*/ + else return relational_type_mismatch(l.type, r.type); return result; } /*}}}*/ @@ -1148,14 +1154,11 @@ Token teq(Token l, Token r) if (len < 3) result.u.integer = 0; } /*}}}*/ - else - { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); - } + else return relational_type_mismatch(l.type, r.type); return result; } /*}}}*/ + /* tabouteq -- ~= operator */ /*{{{*/ Token tabouteq(Token l, Token r) { @@ -1195,7 +1198,7 @@ Token tabouteq(Token l, Token r) else { result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); + result.u.err = strdup(_("Usage: ~= only compares float values")); } return result; } @@ -1278,11 +1281,7 @@ Token tne(Token l, Token r) for (len = 0; len < 3 && l.u.location[len] == r.u.location[len]; ++len); if (len < 3) result.u.integer = 1; } - else - { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("type mismatch for relational operator"))+1),_("type mismatch for relational operator")); - } + else return relational_type_mismatch(l.type, r.type); return result; } /*}}}*/ diff --git a/src/common/eval.h b/src/common/eval.h index 868602f..2b035f6 100644 --- a/src/common/eval.h +++ b/src/common/eval.h @@ -5,6 +5,7 @@ Token tcopy(Token n); void tfree(Token *n); +void tfree_protected(Token *n, const Token dontfree); void tvecfreetoks(Token **tvec); void tvecfree(Token **tvec); size_t tveclen(Token **tvec); diff --git a/src/common/func.c b/src/common/func.c index 7750673..bad5f11 100644 --- a/src/common/func.c +++ b/src/common/func.c @@ -19,6 +19,7 @@ #include <stdlib.h> extern double strtod(const char *nptr, char **endptr); /* SunOS 4 hack */ #include <stdio.h> +extern char *strdup(const char* s); #include <string.h> #include <time.h> @@ -303,7 +304,9 @@ static double deg2rad(double x) } /*}}}*/ -typedef enum {ABSOLUTE, RELATIVE} LocConvention; +typedef enum {ABSOLUTE, RELATIVE, EXCEL} LocConvention; + +static Token excel_adr_func(int argc, const Token argv[]); /* & */ /*{{{*/ static Token adr_func(int argc, const Token argv[], LocConvention lcon) @@ -314,20 +317,23 @@ static Token adr_func(int argc, const Token argv[], LocConvention lcon) size_t i; /*}}}*/ + if (lcon == EXCEL) return excel_adr_func(argc, argv); + /* asserts */ /*{{{*/ assert(argv != (Token*)0); /*}}}*/ LOCATION_GETS(result.u.location, upd_l); if (argc == 1 && argv[0].type == LOCATION) - if (lcon == ABSOLUTE) return argv[0]; - else - { - LOCATION_ADD(result.u.location, argv[0].u.location); - return result; - } + if (lcon == ABSOLUTE) return argv[0]; + else + { + LOCATION_ADD(result.u.location, argv[0].u.location); + return result; + } for (i = 0; i < argc && i < HYPER; ++i) { + if (argv[0].type == EEK) return argv[0]; if (argv[i].type == INT) if (lcon == ABSOLUTE) result.u.location[i] = argv[i].u.integer; else result.u.location[i] += argv[i].u.integer; @@ -355,9 +361,15 @@ static Token at_func(int argc, const Token argv[], LocConvention lcon) Token result; result.type = EEK; char *pref = _("Inside @: "); + if (lcon == RELATIVE) pref = _("Inside R(): "); + if (lcon == EXCEL) pref = _("Inside X(): "); result.u.err = malloc(strlen(location.u.err) + strlen(pref) + 1); strcpy(result.u.err, pref); strcat(result.u.err, location.u.err); + /* don't free the location if it is the same as an argument, because + those get freed later: */ + for (size_t i = 0; i < argc; ++i) + if (argv[i].type == EEK & argv[i].u.err == location.u.err) return result; tfree(&location); return result; } @@ -384,106 +396,104 @@ static Token rel_at_func(int argc, const Token argv[]) return at_func(argc, argv, RELATIVE); } -/* x */ /*{{{*/ -static Token x_func(int argc, const Token argv[]) +static Token excel_at_func(int argc, const Token argv[]) { - /* variables */ /*{{{*/ - Token result; - /*}}}*/ + return at_func(argc, argv, EXCEL); +} - if (argc==0) - /* result is currently updated x position */ /*{{{*/ - { - result.type = INT; - result.u.integer = upd_l[X]; +#define INTPATIBLE(t) (t.type == INT || t.type == EMPTY) + +static Token excel_adr_func(int argc, const Token argv[]) +{ + Token result; + char *usage = _("Usage: X&(THERE_LABEL, HERE_LABEL, [fix_x], [fix_y], [fix_z])"); + if (argc < 2) { + result.type = EEK; + result.u.err = strdup(usage); + return result; } - /*}}}*/ - else if (argc==1 && argv[0].type==LOCATION) - /* return x component of location */ /*{{{*/ + if (argv[0].type == EEK) return argv[0]; + if (argv[1].type == EEK) return argv[1]; + if (argv[0].type != LOCATION || argv[1].type != LOCATION) { - result.type = INT; - result.u.integer = argv[0].u.location[X]; + result.type = EEK; + result.u.err = strdup(usage); + return result; } - /*}}}*/ - else - /* x type error */ /*{{{*/ + + LOCATION_GETS(result.u.location, upd_l); + + for (Dimensions dim = X; dim < HYPER; ++dim) { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("Usage: x([location])"))+1),_("Usage: x([location])")); + int i = dim + 2; + bool fixed = false; + if (i < argc) + { + if (argv[i].type == EEK) return argv[i]; + if (!INTPATIBLE(argv[i])) + { + result.type = EEK; + result.u.err = strdup(usage); + return result; + } + if (argv[i].type == INT && argv[i].u.integer > 0) fixed = true; + } + if (fixed) result.u.location[dim] = argv[0].u.location[dim]; + else result.u.location[dim] += + argv[0].u.location[dim] - argv[1].u.location[dim]; + } + return result; +} + +/* dim_func -- common implementation of the coordinate functions */ /*{{{*/ +static Token dim_func(int argc, const Token argv[], Dimensions dim) +{ + Token result; + result.type = INT; + + if (argc == 0) + /* result is currently updated coordinate along dim */ /*{{{*/ + result.u.integer = upd_l[dim]; + /*}}}*/ + else if (argc == 1 && argv[0].type == LOCATION) + /* return dim coordinate of location */ /*{{{*/ + result.u.integer = argv[0].u.location[dim]; + else /* type error */ /*{{{*/ + { + result.type = EEK; + char *templ = _("Usage: %c([location])"); + const char *DIMS = "xyz"; + result.u.err = malloc(strlen(templ)+1); + sprintf(result.u.err, templ, DIMS[dim]); } /*}}}*/ return result; } +/*}}}/ + +/* x */ /*{{{*/ +static Token x_func(int argc, const Token argv[]) +{ + return dim_func(argc, argv, X); +} /*}}}*/ /* y */ /*{{{*/ static Token y_func(int argc, const Token argv[]) { - /* variables */ /*{{{*/ - Token result; - /*}}}*/ - - if (argc==0) - /* result is currently updated y position */ /*{{{*/ - { - result.type = INT; - result.u.integer = upd_l[Y]; - } - /*}}}*/ - else if (argc==1 && argv[0].type==LOCATION) - /* return y component of location */ /*{{{*/ - { - result.type = INT; - result.u.integer = argv[0].u.location[Y]; - } - /*}}}*/ - else - /* y type error */ /*{{{*/ - { - result.type=EEK; - result.u.err=strcpy(malloc(strlen(_("Usage: y([location])"))+1),_("Usage: y([location])")); - } - /*}}}*/ - return result; + return dim_func(argc, argv, Y); } /*}}}*/ /* z */ /*{{{*/ static Token z_func(int argc, const Token argv[]) { - /* variables */ /*{{{*/ - Token result; - /*}}}*/ - - if (argc==0) - /* result is currently updated z position */ /*{{{*/ - { - result.type = INT; - result.u.integer = upd_l[Z]; - } - /*}}}*/ - else if (argc == 1 && argv[0].type == LOCATION) - /* return z component of location */ /*{{{*/ - { - result.type = INT; - result.u.integer = argv[0].u.location[Z]; - } - /*}}}*/ - else - /* result is z type error */ /*{{{*/ - { - result.type=EEK; - result.u.err=mystrmalloc(_("Usage: z([location])")); - } - /*}}}*/ - return result; + return dim_func(argc, argv, Z); } /*}}}*/ typedef enum { LOG_AND, LOG_OR } LogicalFunction; -#define INTPATIBLE(t) (t.type == INT || t.type == EMPTY) - static Token bitwise_func(int argc, const Token argv[], LogicalFunction lop) { Token result; @@ -495,7 +505,7 @@ static Token bitwise_func(int argc, const Token argv[], LogicalFunction lop) { result.type = EEK; result.u.err = - mystrmalloc(_("Bitwise functions operate only on integers.")); + strdup(_("Bitwise functions operate only on integers.")); return result; } int val = 0; @@ -534,7 +544,7 @@ static Token e_func(int argc, const Token argv[]) else /* result is e type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: e()")); + result.u.err=strdup(_("Usage: e()")); } /*}}}*/ return result; @@ -552,7 +562,7 @@ static Token eval_func(int argc, const Token argv[]) /* nesting error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("nested eval()")); + result.u.err=strdup(_("nested eval()")); } /*}}}*/ else if (argc==1 && argv[0].type==LOCATION) @@ -806,12 +816,12 @@ static Token int_func(int argc, const Token argv[]) if (s==(char*)0 || *s) { result.type=EEK; - result.u.err=mystrmalloc(_("int(string): invalid string")); + result.u.err=strdup(_("int(string): invalid string")); } else if (errno==ERANGE && (result.u.integer==LONG_MAX || result.u.integer==LONG_MIN)) { result.type=EEK; - result.u.err=mystrmalloc(_("int(string): domain error")); + result.u.err=strdup(_("int(string): domain error")); } else result.type=INT; } @@ -869,7 +879,7 @@ static Token len_func(int argc, const Token argv[]) /* result is frac type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: len(string)")); + result.u.err=strdup(_("Usage: len(string)")); } /*}}}*/ return result; @@ -912,7 +922,7 @@ static Token log_func(int argc, const Token argv[]) else /* result is log type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: log(float[,float])")); + result.u.err=strdup(_("Usage: log(float[,float])")); } /*}}}*/ return result; @@ -998,7 +1008,7 @@ static Token minmax_func(int argc, const Token argv[], int min) /* result is min/max type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(min ? _("Usage: min(location,location) or min(val1, val2,...)") : _("Usage: max(location,location) or max(val1,val2,...)")); + result.u.err=strdup(min ? _("Usage: min(location,location) or min(val1, val2,...)") : _("Usage: max(location,location) or max(val1,val2,...)")); return result; } /*}}}*/ @@ -1041,7 +1051,7 @@ static Token abs_func(int argc, const Token argv[]) /* result is abs type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: abs(float|integer)")); + result.u.err=strdup(_("Usage: abs(float|integer)")); } /*}}}*/ return result; @@ -1060,12 +1070,12 @@ static Token env_func(int argc, const Token argv[]) if ((e=getenv(argv[0].u.string))==(char*)0) e=""; result.type=STRING; - result.u.string=mystrmalloc(e); + result.u.string=strdup(e); } else { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: $(string)")); + result.u.err=strdup(_("Usage: $(string)")); } return result; } @@ -1088,7 +1098,7 @@ static Token float_func(int argc, const Token argv[]) else { result.type=EEK; - result.u.err=mystrmalloc(_("Not a (finite) floating point number")); + result.u.err=strdup(_("Not a (finite) floating point number")); } } /*}}}*/ @@ -1096,7 +1106,7 @@ static Token float_func(int argc, const Token argv[]) /* float type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: float(string)")); + result.u.err=strdup(_("Usage: float(string)")); } /*}}}*/ return result; @@ -1119,7 +1129,7 @@ static Token strftime_func(int argc, const Token argv[]) tm=localtime(&t); strftime(s,sizeof(s),argv[0].u.string,tm); s[sizeof(s)-1]='\0'; - result.u.string=mystrmalloc(s); + result.u.string=strdup(s); result.type=STRING; } /*}}}*/ @@ -1133,14 +1143,14 @@ static Token strftime_func(int argc, const Token argv[]) tm=localtime(&t); strftime(s,sizeof(s),argv[0].u.string,tm); s[sizeof(s)-1]='\0'; - result.u.string=mystrmalloc(s); + result.u.string=strdup(s); result.type=STRING; } /*}}}*/ else /* strftime type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: strftime(string[,integer])")); + result.u.err=strdup(_("Usage: strftime(string[,integer])")); } /*}}}*/ return result; @@ -1184,7 +1194,7 @@ static Token clock_func(int argc, const Token argv[]) else /* wrong usage */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: clock(condition,location[,location])")); + result.u.err=strdup(_("Usage: clock(condition,location[,location])")); } /*}}}*/ return result; @@ -1322,7 +1332,7 @@ static Token rnd_func(int argc, const Token argv[]) else { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: rnd()")); + result.u.err=strdup(_("Usage: rnd()")); } return result; } @@ -1351,7 +1361,7 @@ static Token substr_func(int argc, const Token argv[]) ss[n] = '\0'; strncpy(ss, argv[0].u.string + b, n); result.type=STRING; - result.u.string=mystrmalloc(ss); + result.u.string=strdup(ss); } else { result.type=EMPTY; @@ -1360,7 +1370,7 @@ static Token substr_func(int argc, const Token argv[]) else { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: substr(string,integer,integer)")); + result.u.err=strdup(_("Usage: substr(string,integer,integer)")); } return result; } @@ -1387,7 +1397,7 @@ static Token strptime_func(int argc, const Token argv[]) else /* strftime type error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: strptime(string,string)")); + result.u.err=strdup(_("Usage: strptime(string,string)")); } /*}}}*/ return result; @@ -1406,7 +1416,7 @@ static Token time_func(int argc, const Token argv[]) else { result.type=EEK; - result.u.err=mystrmalloc(_("Usage: time()")); + result.u.err=strdup(_("Usage: time()")); } return result; } @@ -1460,8 +1470,10 @@ Tfunc tfunc[]= { "time", time_func }, { "bitand", bitand_func }, { "bitor", bitor_func }, - { "D", rel_adr_func }, { "R", rel_at_func }, + { "D", rel_adr_func }, + { "X", excel_at_func }, + { "X&", excel_adr_func }, { "", (Token (*)(int, const Token[]))0 } }; /*}}}*/ diff --git a/src/common/main.c b/src/common/main.c index 074b915..25cd801 100644 --- a/src/common/main.c +++ b/src/common/main.c @@ -24,6 +24,7 @@ extern char *optarg; extern int optind,opterr,optopt; int getopt(int argc, char * const *argv, const char *optstring); +extern char *strdup(const char* s); #include <string.h> #include <unistd.h> @@ -680,9 +681,9 @@ static int do_savecsv(Sheet *cursheet, const char *name) MenuChoice menu[4]; name = cursheet->name; - menu[0].str=mystrmalloc(_("cC)omma (,)")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("sS)emicolon (;)")); menu[1].c='\0'; - menu[2].str=mystrmalloc(_("tT)ab (\\t)")); menu[2].c='\0'; + menu[0].str=strdup(_("cC)omma (,)")); menu[0].c='\0'; + menu[1].str=strdup(_("sS)emicolon (;)")); menu[1].c='\0'; + menu[2].str=strdup(_("tT)ab (\\t)")); menu[2].c='\0'; menu[3].str=(char*)0; sep=line_menu(_("Choose separator:"),menu,0); if (sep < 0) return sep; @@ -912,9 +913,9 @@ static int do_insert(Sheet *sheet) { MenuChoice menu[4]; - menu[0].str=mystrmalloc(_("cC)olumn")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("rR)ow")); menu[1].c='\0'; - menu[2].str=mystrmalloc(_("dD)epth")); menu[2].c='\0'; + menu[0].str=strdup(_("cC)olumn")); menu[0].c='\0'; + menu[1].str=strdup(_("rR)ow")); menu[1].c='\0'; + menu[2].str=strdup(_("dD)epth")); menu[2].c='\0'; menu[3].str=(char*)0; reply=line_menu(_("Insert:"),menu,0); free(menu[0].str); @@ -938,12 +939,12 @@ static int do_insert(Sheet *sheet) /* show menu */ /*{{{*/ switch (reply) { - case 0: menu[0].str=mystrmalloc(_("wW)hole column")); break; - case 1: menu[0].str=mystrmalloc(_("wW)hole line")); break; - case 2: menu[0].str=mystrmalloc(_("wW)hole sheet")); break; + case 0: menu[0].str=strdup(_("wW)hole column")); break; + case 1: menu[0].str=strdup(_("wW)hole line")); break; + case 2: menu[0].str=strdup(_("wW)hole sheet")); break; default: assert(0); } - menu[1].str=mystrmalloc(_("sS)ingle cell")); menu[1].c='\0'; + menu[1].str=strdup(_("sS)ingle cell")); menu[1].c='\0'; menu[2].str=(char*)0; r=line_menu(_("Insert:"),menu,0); free(menu[0].str); @@ -1029,9 +1030,9 @@ static int do_delete(Sheet *sheet) { MenuChoice menu[4]; - menu[0].str=mystrmalloc(_("cC)olumn")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("rR)ow")); menu[1].c='\0'; - menu[2].str=mystrmalloc(_("dD)epth")); menu[2].c='\0'; + menu[0].str=strdup(_("cC)olumn")); menu[0].c='\0'; + menu[1].str=strdup(_("rR)ow")); menu[1].c='\0'; + menu[2].str=strdup(_("dD)epth")); menu[2].c='\0'; menu[3].str=(char*)0; reply=line_menu(_("Delete:"),menu,0); free(menu[0].str); @@ -1056,12 +1057,12 @@ static int do_delete(Sheet *sheet) /* show menu */ /*{{{*/ switch (reply) { - case 0: menu[0].str=mystrmalloc(_("wW)hole column")); break; - case 1: menu[0].str=mystrmalloc(_("wW)hole line")); break; - case 2: menu[0].str=mystrmalloc(_("wW)hole sheet")); break; + case 0: menu[0].str=strdup(_("wW)hole column")); break; + case 1: menu[0].str=strdup(_("wW)hole line")); break; + case 2: menu[0].str=strdup(_("wW)hole sheet")); break; default: assert(0); } - menu[1].str=mystrmalloc(_("sS)ingle cell")); menu[1].c='\0'; + menu[1].str=strdup(_("sS)ingle cell")); menu[1].c='\0'; menu[2].str=(char*)0; r=line_menu(_("Delete:"),menu,0); free(menu[0].str); @@ -1233,15 +1234,15 @@ static int do_sort(Sheet *sheet) do_mark(sheet, GET_MARK_ALL); /* build menues */ /*{{{*/ - menu1[0].str=mystrmalloc(_("cC)olumn")); menu1[0].c='\0'; - menu1[1].str=mystrmalloc(_("rR)ow")); menu1[1].c='\0'; - menu1[2].str=mystrmalloc(_("dD)epth")); menu1[2].c='\0'; + menu1[0].str=strdup(_("cC)olumn")); menu1[0].c='\0'; + menu1[1].str=strdup(_("rR)ow")); menu1[1].c='\0'; + menu1[2].str=strdup(_("dD)epth")); menu1[2].c='\0'; menu1[3].str=(char*)0; - menu2[0].str=mystrmalloc(_("sS)ort region")); menu2[0].c='\0'; - menu2[1].str=mystrmalloc(_("aA)dd key")); menu2[1].c='\0'; + menu2[0].str=strdup(_("sS)ort region")); menu2[0].c='\0'; + menu2[1].str=strdup(_("aA)dd key")); menu2[1].c='\0'; menu2[2].str=(char*)0; - menu3[0].str=mystrmalloc(_("aA)scending")); menu3[0].c='\0'; - menu3[1].str=mystrmalloc(_("dD)escending")); menu3[0].c='\0'; + menu3[0].str=strdup(_("aA)scending")); menu3[0].c='\0'; + menu3[1].str=strdup(_("dD)escending")); menu3[0].c='\0'; menu3[2].str=(char*)0; /*}}}*/ @@ -1453,9 +1454,9 @@ static int do_mirror(Sheet *sheet) { MenuChoice menu[4]; - menu[0].str=mystrmalloc(_("lL)eft-right")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("uU)pside-down")); menu[1].c='\0'; - menu[2].str=mystrmalloc(_("fF)ront-back")); menu[2].c='\0'; + menu[0].str=strdup(_("lL)eft-right")); menu[0].c='\0'; + menu[1].str=strdup(_("uU)pside-down")); menu[1].c='\0'; + menu[2].str=strdup(_("fF)ront-back")); menu[2].c='\0'; menu[3].str=(char*)0; reply=line_menu(_("Mirror block:"),menu,0); free(menu[0].str); @@ -1882,7 +1883,7 @@ int main(int argc, char *argv[]) { const char *msg; - cursheet->name=mystrmalloc(loadfile); + cursheet->name=strdup(loadfile); if (usexdr) { if ((msg=loadxdr(cursheet,cursheet->name))!=(const char*)0) line_msg(_("Load sheet from XDR file:"),msg); diff --git a/src/common/misc.c b/src/common/misc.c index ef28123..ef9a3c1 100644 --- a/src/common/misc.c +++ b/src/common/misc.c @@ -88,12 +88,7 @@ void posorder(int *x, int *y) } } /*}}}*/ -/* mystrmalloc -- return malloced copy of string */ /*{{{*/ -char *mystrmalloc(const char *str) -{ - return (strcpy(malloc(strlen(str)+1),str)); -} -/*}}}*/ + /* finite -- return error message about number or null */ /*{{{*/ static volatile int caughtfpe; diff --git a/src/common/misc.h b/src/common/misc.h index db0d24b..f7ed7de 100644 --- a/src/common/misc.h +++ b/src/common/misc.h @@ -11,7 +11,6 @@ extern "C" { void posorder(int *x, int *y); long int posnumber(const char *s, const char **endptr); -char *mystrmalloc(const char *str); const char *dblfinite(double x); int fputc_close(char c, FILE *fp); int fputs_close(const char *s, FILE *fp); diff --git a/src/common/parser.c b/src/common/parser.c index 9338bf2..c9063bb 100644 --- a/src/common/parser.c +++ b/src/common/parser.c @@ -12,8 +12,10 @@ #include <assert.h> #include <ctype.h> +#include <float.h> #include <stdio.h> #include <stdlib.h> +extern char *strdup(const char* s); #include <string.h> @@ -89,7 +91,7 @@ static Token primary(Token *n[], int *i) /* return error, value expected */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("value expected")); + result.u.err=strdup(_("value expected")); return result; } /*}}}*/ @@ -153,8 +155,10 @@ static Token primary(Token *n[], int *i) /* eval function */ /*{{{*/ { ++(*i); - result=tfuncall(ident,argc,argv); - for (j=0; j<argc; ++j) tfree(&argv[j]); + result = tfuncall(ident,argc,argv); + /* To allow a function to return one of its arguments, we need + to be sure not to free that argument: */ + for (j=0; j<argc; ++j) tfree_protected(&argv[j], result); } /*}}}*/ else @@ -171,7 +175,7 @@ static Token primary(Token *n[], int *i) else { result.type=EEK; - result.u.err=mystrmalloc(_("( expected")); + result.u.err=strdup(_("( expected")); return result; } } @@ -179,7 +183,7 @@ static Token primary(Token *n[], int *i) default: ; /* fall through */ } result.type=EEK; - result.u.err=mystrmalloc(_("value expected")); + result.u.err=strdup(_("value expected")); return result; } /*}}}*/ diff --git a/src/common/scanner.h b/src/common/scanner.h index 23f52a5..dbb713f 100644 --- a/src/common/scanner.h +++ b/src/common/scanner.h @@ -15,6 +15,7 @@ typedef enum { #endif } Type; +#define MAX_TYPE_NAME_LENGTH 16 extern const char *Type_Name[]; typedef enum { PLUS, MINUS, MUL, DIV, OP, CP, COMMA, LT, LE, GE, GT, ISEQUAL, ABOUTEQ, NE, POW, MOD } Operator; diff --git a/src/common/sheet.c b/src/common/sheet.c index 88ab92f..32aff69 100644 --- a/src/common/sheet.c +++ b/src/common/sheet.c @@ -538,7 +538,9 @@ Token getvalue(Sheet *sheet, const Location at) /* return error */ /*{{{*/ { result.type=EEK; - result.u.err=mystrmalloc(_("Negative index")); + char *templ = _("Attempt to access cell at (%d,%d,%d) with negative index"); + result.u.err = malloc(strlen(templ)+32); + sprintf(result.u.err, templ, at[X], at[Y], at[Z]); return result; } /*}}}*/ @@ -831,7 +833,10 @@ Token findlabel(Sheet *sheet, const char *label) else { result.type = EEK; - result.u.err = mystrmalloc(_("No such label")); + char *pref = _("No such label: "); + result.u.err = malloc(strlen(pref) + strlen(label) + 1); + strcpy(result.u.err, pref); + strcat(result.u.err, label); } return result; } @@ -860,7 +865,7 @@ void relabel(Sheet *sheet, const Location at, if ((*run)->type==LIDENT && strcmp((*run)->u.lident, oldlabel)==0) { free((*run)->u.lident); - (*run)->u.lident = mystrmalloc(newlabel); + (*run)->u.lident = strdup(newlabel); } cachelabels(sheet); forceupdate(sheet); @@ -1337,7 +1342,7 @@ const char *loadport(Sheet *sheet, const char *name) ++ns; while (*ns && *ns!=' ') { *p=*ns; ++p; ++ns; } *p='\0'; - loaded.label=mystrmalloc(buf); + loaded.label=strdup(buf); break; } /*}}}*/ @@ -1683,7 +1688,7 @@ const char *loadcsv(Sheet *sheet, const char *name) if (s!=cend) /* ok, it is a string */ /*{{{*/ { t[0]->type=STRING; - t[0]->u.string=mystrmalloc(str); + t[0]->u.string=strdup(str); putcont(sheet, where, t, BASE); } /*}}}*/ diff --git a/src/common/wk1.c b/src/common/wk1.c index 5713058..89f83ea 100644 --- a/src/common/wk1.c +++ b/src/common/wk1.c @@ -12,6 +12,7 @@ #include <math.h> #include <stdio.h> #include <stdlib.h> +extern char *strdup(const char *s); #include <string.h> #ifdef OLD_REALLOC #define realloc(s,l) myrealloc(s,l) @@ -891,7 +892,7 @@ const char *loadwk1(Sheet *sheet, const char *name) t[0]=malloc(sizeof(Token)); t[1]=(Token*)0; t[0]->type=STRING; - t[0]->u.string=mystrmalloc(body+6); + t[0]->u.string=strdup(body+6); putcont(sheet, tmp, t, BASE); format((unsigned char)body[0], cell); break; @@ -1098,7 +1099,7 @@ const char *loadwk1(Sheet *sheet, const char *name) t[0]=malloc(sizeof(Token)); t[1]=(Token*)0; t[0]->type=STRING; - t[0]->u.string=mystrmalloc(body+5); + t[0]->u.string=strdup(body+5); putcont(sheet, tmp, t, BASE); format((unsigned char)body[0], cell); break; diff --git a/src/display.c b/src/display.c index fed3697..51b2a0c 100644 --- a/src/display.c +++ b/src/display.c @@ -22,6 +22,7 @@ #include <stdbool.h> #include <stdio.h> #include <stdlib.h> +extern char *strdup(const char* s); #include <string.h> #include <unistd.h> #ifdef NEED_BCOPY @@ -66,21 +67,21 @@ static int do_attribute(Sheet *cursheet) int c; /* create menus */ - adjmenu[0].str=mystrmalloc(_("lL)eft")); adjmenu[0].c='\0'; - adjmenu[1].str=mystrmalloc(_("rR)ight")); adjmenu[1].c='\0'; - adjmenu[2].str=mystrmalloc(_("cC)entered")); adjmenu[2].c='\0'; - adjmenu[3].str=mystrmalloc(_("11).23e1 <-> 12.3")); adjmenu[3].c='\0'; - adjmenu[4].str=mystrmalloc(_("pP)recision")); adjmenu[4].c='\0'; - adjmenu[5].str=mystrmalloc(_("sS)hadow")); adjmenu[5].c='\0'; - adjmenu[6].str=mystrmalloc(_("bB)old")); adjmenu[6].c='\0'; - adjmenu[7].str=mystrmalloc(_("uU)nderline")); adjmenu[7].c='\0'; - adjmenu[8].str=mystrmalloc(_("oO)utput special characters")); adjmenu[8].c='\0'; + adjmenu[0].str=strdup(_("lL)eft")); adjmenu[0].c='\0'; + adjmenu[1].str=strdup(_("rR)ight")); adjmenu[1].c='\0'; + adjmenu[2].str=strdup(_("cC)entered")); adjmenu[2].c='\0'; + adjmenu[3].str=strdup(_("11).23e1 <-> 12.3")); adjmenu[3].c='\0'; + adjmenu[4].str=strdup(_("pP)recision")); adjmenu[4].c='\0'; + adjmenu[5].str=strdup(_("sS)hadow")); adjmenu[5].c='\0'; + adjmenu[6].str=strdup(_("bB)old")); adjmenu[6].c='\0'; + adjmenu[7].str=strdup(_("uU)nderline")); adjmenu[7].c='\0'; + adjmenu[8].str=strdup(_("oO)utput special characters")); adjmenu[8].c='\0'; adjmenu[9].str=(char*)0; - mainmenu[0].str=mystrmalloc(_("rR)epresentation")); mainmenu[0].c='\0'; - mainmenu[1].str=mystrmalloc(_("lL)abel")); mainmenu[1].c='\0'; - mainmenu[2].str=mystrmalloc(_("oLo)ck")); mainmenu[2].c='\0'; - mainmenu[3].str=mystrmalloc(_("iI)gnore")); mainmenu[3].c='\0'; + mainmenu[0].str=strdup(_("rR)epresentation")); mainmenu[0].c='\0'; + mainmenu[1].str=strdup(_("lL)abel")); mainmenu[1].c='\0'; + mainmenu[2].str=strdup(_("oLo)ck")); mainmenu[2].c='\0'; + mainmenu[3].str=strdup(_("iI)gnore")); mainmenu[3].c='\0'; mainmenu[4].str=(char*)0; do @@ -155,9 +156,9 @@ static int do_file(Sheet *cursheet) int c; - menu[0].str=mystrmalloc(_("lL)oad")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("sS)ave")); menu[1].c='\0'; - menu[2].str=mystrmalloc(_("nN)ame")); menu[2].c='\0'; + menu[0].str=strdup(_("lL)oad")); menu[0].c='\0'; + menu[1].str=strdup(_("sS)ave")); menu[1].c='\0'; + menu[2].str=strdup(_("nN)ame")); menu[2].c='\0'; menu[3].str=(char*)0; c=0; do @@ -257,14 +258,14 @@ static int do_block(Sheet *cursheet) MenuChoice block[9]; int c; - block[0].str=mystrmalloc(_("ecle)ar")); block[0].c='\0'; - block[1].str=mystrmalloc(_("iI)nsert")); block[1].c='\0'; - block[2].str=mystrmalloc(_("dD)elete")); block[2].c='\0'; - block[3].str=mystrmalloc(_("mM)ove")); block[3].c='\0'; - block[4].str=mystrmalloc(_("cC)opy")); block[4].c='\0'; - block[5].str=mystrmalloc(_("fF)ill")); block[5].c='\0'; - block[6].str=mystrmalloc(_("sS)ort")); block[6].c='\0'; - block[7].str=mystrmalloc(_("rMir)ror")); block[7].c='\0'; + block[0].str=strdup(_("ecle)ar")); block[0].c='\0'; + block[1].str=strdup(_("iI)nsert")); block[1].c='\0'; + block[2].str=strdup(_("dD)elete")); block[2].c='\0'; + block[3].str=strdup(_("mM)ove")); block[3].c='\0'; + block[4].str=strdup(_("cC)opy")); block[4].c='\0'; + block[5].str=strdup(_("fF)ill")); block[5].c='\0'; + block[6].str=strdup(_("sS)ort")); block[6].c='\0'; + block[7].str=strdup(_("rMir)ror")); block[7].c='\0'; block[8].str=(char*)0; c=0; do @@ -303,14 +304,14 @@ int show_menu(Sheet *cursheet) int c = K_INVALID; - menu[0].str=mystrmalloc(_("aA)ttributes")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("wW)idth")); menu[1].c='\0'; - menu[2].str=mystrmalloc(_("bB)lock")); menu[2].c='\0'; - menu[3].str=mystrmalloc(_("fF)ile")); menu[3].c='\0'; - menu[4].str=mystrmalloc(_("gG)oto")); menu[4].c='\0'; - menu[5].str=mystrmalloc(_("sS)hell")); menu[5].c='\0'; - menu[6].str=mystrmalloc(_("vV)ersion")); menu[6].c='\0'; - menu[7].str=mystrmalloc(_("qQ)uit")); menu[7].c='\0'; + menu[0].str=strdup(_("aA)ttributes")); menu[0].c='\0'; + menu[1].str=strdup(_("wW)idth")); menu[1].c='\0'; + menu[2].str=strdup(_("bB)lock")); menu[2].c='\0'; + menu[3].str=strdup(_("fF)ile")); menu[3].c='\0'; + menu[4].str=strdup(_("gG)oto")); menu[4].c='\0'; + menu[5].str=strdup(_("sS)hell")); menu[5].c='\0'; + menu[6].str=strdup(_("vV)ersion")); menu[6].c='\0'; + menu[7].str=strdup(_("qQ)uit")); menu[7].c='\0'; menu[8].str=(char*)0; do @@ -838,8 +839,8 @@ int line_ok(const char *prompt, int curx) assert(curx==0 || curx==1); - menu[0].str=mystrmalloc(_("nN)o")); menu[0].c='\0'; - menu[1].str=mystrmalloc(_("yY)es")); menu[1].c='\0'; + menu[0].str=strdup(_("nN)o")); menu[0].c='\0'; + menu[1].str=strdup(_("yY)es")); menu[1].c='\0'; menu[2].str=(char*)0; result=line_menu(prompt,menu,curx); free(menu[0].str);