diff --git a/README.md b/README.md index 96eff7e..597d56e 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,62 @@ $ clac "1 2 3 4 count . sum , /" In fact, if you find yourself calculating averages very often, you can define the word `avg` as `"count . sum , /"`. +### Separator modes + +In many countries the comma is the decimal separator. +Furthermore a thousands separator is often used (in India it's a bit +more complicated, but let's still call it like that). +The following could be prices in three different countries: + +* `1,234,567.89 $` +* `1.234.567,89 €` +* `12.34.56.789 ₹` + +Clac has a total of 5 separator modes, that can be selected by +starting it with one of the following options (if more are provided, +the last one wins; if none is provided, `-d` is implied): + +* `-b`: "both" mode: both the `.` and the `,` are accepted as + decimal separators. No thousands separator can be used. + In the output, the `.` is used as the decimal separator. + +* `-c`: "comma" mode: only the `,` is accepted as the decimal + separator. No thousands separator can be used. + In the output, the `,` is used as the decimal separator. + +* `-C`: "super comma" mode: the `,` is the decimal + separator, while the `.` is completely ignored in numbers. + In the output, the `,` is used as the decimal separator. + +* `-d`: "dot" mode (default): only the `.` is accepted as the + decimal separator. No thousands separator can be used. + In the output, the `.` is used as the decimal separator. + +* `-D`: "super dot" mode: the `.` is the decimal + separator, while the `,` is completely ignored in numbers. + In the output, the `.` is used as the decimal separator. + +```shell +$ clac -b "1.2 3,4 *" +4.08 + +$ clac -c "1,2 3,4 *" +4,08 + +$ clac -C "1.234.567,89" +1234567,89 + +$ clac -D "1,234,567.89" +1234567.89 +``` + +The definition of words in the configuration file is always parsed +in "dot" mode, i.e., numbers therein may only use the dot as the +decimal separator and may not use any thousands separator. +This way the configuration file doesn't change its +meaning depending on the options clac is started with. + + Contributing ------------ diff --git a/clac.1 b/clac.1 index ad6a412..fe42b2c 100644 --- a/clac.1 +++ b/clac.1 @@ -9,6 +9,7 @@ .Sh SYNOPSIS . .Nm +.Op Fl bcCdD .Op Ar expression . .Sh DESCRIPTION @@ -300,5 +301,38 @@ define the word as .Qq Sy "count . sum , /" . . +.Sh OPTIONS +.Bl -tag -width 6n +.It Fl b +"both" mode: both the `.` and the `,` are accepted as decimal separators. +No thousands separator can be used. +In the output, the `.` is used as the decimal separator. +. +.It Fl c +"comma" mode: only the `,` is accepted as the decimal separator. +No thousands separator can be used. +In the output, the `,` is used as the decimal separator. +. +.It Fl C +"super comma" mode: the `,` is the decimal separator, +while the `.` is completely ignored in numbers. +In the output, the `,` is used as the decimal separator. +. +.It Fl d +"dot" mode (default): only the `.` is accepted as the decimal separator. +No thousands separator can be used. +In the output, the `.` is used as the decimal separator. +. +.It Fl D +"super dot" mode: the `.` is the decimal separator, +while the `,` is completely ignored in numbers. +In the output, the `.` is used as the decimal separator. +. +.El +.Pp +The default separator mode is +.Fl d . +If more than one mode is provided, the last one wins. +. .Sh AUTHOR .An Michel Martens Aq mail@soveran.com diff --git a/clac.c b/clac.c index 62e389e..9f4c10d 100644 --- a/clac.c +++ b/clac.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "linenoise.h" #include "sds.h" @@ -40,7 +41,8 @@ /* UI */ #define HINT_COLOR 33 #define NUMBER_FMT "%.15g" -#define OUTPUT_FMT "\x1b[33m= " NUMBER_FMT "\x1b[0m\n" +#define NUMBER_FMT_MAX_STRLEN 22 +#define OUTPUT_FMT "\x1b[33m= %s\x1b[0m\n" #define WORDEF_FMT "%s \x1b[33m\"%s\"\x1b[0m\n" /* Config */ @@ -75,6 +77,7 @@ static node *head = NULL; static node *tail = NULL; static sds result; static double hole = 0; +static char mode = 'd'; static int isoverflow(stack *s) { if (isfull(s)) { @@ -272,11 +275,27 @@ static void load(sds filename) { sdsfree(content); } -static void eval(const char *input); +static char *number(double dbl) { + static char buffer[NUMBER_FMT_MAX_STRLEN + 1]; + char *c; -static void process(sds word) { + sprintf(buffer, NUMBER_FMT, dbl); + + if (mode == 'c' || mode == 'C') { + for (c = buffer; *c; c++) { + if (*c == '.') { + *c = ','; + } + } + } + return buffer; +} + +static void eval(const char *input, int toplevel); + +static void process(sds word, int toplevel) { double a, b; - char *z; + char *c, *d, *z; node *n; if (!strcmp(word, "_")) { @@ -420,9 +439,29 @@ static void process(sds word) { move(s0, s1, count(s0)); } else if (!strcasecmp(word, ";")) { move(s1, s0, count(s1)); - } else if ((n = get(word)) != NULL) { - eval(n->meaning); + } else if ((n = get(word)) != NULL) { + eval(n->meaning, 0); } else { + if (toplevel && mode != 'd') { + for (d = c = word; *c; c++) { + if (*c == ',') { + if (mode == 'b' || mode == 'c' || mode == 'C') { + *d++ = '.'; + } else if (mode != 'D') { + *d++ = ','; + } + } else if (*c == '.') { + if (mode == 'b' || mode == 'd' || mode == 'D') { + *d++ = '.'; + } else if (mode != 'C') { + *d++ = ','; + } + } else { + *d++ = *c; + } + } + *d = '\0'; + } a = strtod(word, &z); if (*z == '\0') { @@ -433,13 +472,13 @@ static void process(sds word) { } } -static void eval(const char *input) { +static void eval(const char *input, int toplevel) { int i, argc; sds *argv = sdssplitargs(input, &argc); for (i = 0; i < argc; i++) { - process(argv[i]); + process(argv[i], toplevel); } sdsfreesplitres(argv, argc); @@ -453,20 +492,20 @@ static char *hints(const char *input, int *color, int *bold) { clear(s0); clear(s1); - eval(input); + eval(input, 1); sdsclear(result); result = sdscat(result, " "); for (i = 0; i < count(s0); i++) { - result = sdscatprintf(result, " " NUMBER_FMT, s0->items[i]); + result = sdscatprintf(result, " %s", number(s0->items[i])); } if (!isempty(s1)) { result = sdscat(result, " ⋮"); for (i = s1->top-1; i > -1; i--) { - result = sdscatprintf(result, " " NUMBER_FMT, s1->items[i]); + result = sdscatprintf(result, " %s", number(s1->items[i])); } } @@ -497,27 +536,40 @@ static void config() { } int main(int argc, char **argv) { - char *line; + char *line, *expr = NULL; + int i, j; + + setlocale(LC_NUMERIC, "C"); result = sdsempty(); config(); - if (argc == 2) { - eval(argv[1]); + for (i = 1; i < argc; i++) { + if (strlen(argv[i]) > 1 && argv[i][0] == '-' && isalpha(argv[i][1])) { + for (j = 1; j < strlen(argv[i]); j++) { + if (strchr("bcCdD", argv[i][j]) == NULL) { + goto usage_error; + } + mode = argv[i][j]; + } + } else if (expr == NULL) { + expr = argv[i]; + } else { + goto usage_error; + } + } + + if (expr != NULL) { + eval(expr, 1); while (count(s0) > 0) { - printf(NUMBER_FMT "\n", pop(s0)); + printf("%s\n", number(pop(s0))); } exit(0); } - if (argc > 2) { - fprintf(stderr, "usage: clac [expression]\n"); - exit(1); - } - linenoiseSetHintsCallback(hints); linenoiseSetCompletionCallback(completion); @@ -535,7 +587,7 @@ int main(int argc, char **argv) { } else if (!isempty(s0)) { hole = peek(s0); clear(s0); - printf(OUTPUT_FMT, hole); + printf(OUTPUT_FMT, number(hole)); } sdsclear(result); @@ -547,4 +599,8 @@ int main(int argc, char **argv) { cleanup(); return 0; + +usage_error: + fprintf(stderr, "usage: clac [-bcCdD] [expression]\n"); + exit(1); } diff --git a/test/tests.sh b/test/tests.sh index 81564ce..e5f0c79 100755 --- a/test/tests.sh +++ b/test/tests.sh @@ -33,8 +33,35 @@ assert_equal "nan" `./clac 3+` # Not found words starting with alpha are ignored assert_equal "" `./clac foo` -# Argument error (too many arguments) -assert_equal "" `./clac 1 2 2> /dev/null` +# Usage errors +assert_equal "usage:" `./clac 1 2 2>&1` # too many arguments +assert_equal "usage:" `./clac -o 2>&1` # unknown argument +assert_equal "usage:" `./clac -co 2>&1` # known and unknown argument + +# Separator modes +assert_equal "1234.99" `./clac -b "1234.99"` +assert_equal "1234.99" `./clac -b "1234,99"` +assert_equal "nan" `./clac -c "1234.99"` +assert_equal "1234,99" `./clac -c "1234,99"` +assert_equal "1234.99" `./clac -d "1234.99"` +assert_equal "nan" `./clac -d "1234,99"` +assert_equal "123499" `./clac -C "1234.99"` +assert_equal "1234,99" `./clac -C "1234,99"` +assert_equal "1234.99" `./clac -D "1234.99"` +assert_equal "123499" `./clac -D "1234,99"` +assert_equal "nan" `./clac -b "1,234.99"` +assert_equal "nan" `./clac -b "1.234,99"` +assert_equal "nan" `./clac -c "1,234.99"` +assert_equal "nan" `./clac -c "1.234,99"` +assert_equal "nan" `./clac -d "1,234.99"` +assert_equal "nan" `./clac -d "1.234,99"` +assert_equal "1,23499" `./clac -C "1,234.99"` +assert_equal "1234,99" `./clac -C "1.234,99"` +assert_equal "1234.99" `./clac -D "1,234.99"` +assert_equal "1.23499" `./clac -D "1.234,99"` +assert_equal "3001000.08" `./clac -b "1000,2 3000.4 *"` +assert_equal "0,003141592" `./clac -c "pi 0,001 *"` +assert_equal "3141,592" `./clac -C "pi 1.000,000 *"` # Stashing numbers assert_equal "21" `./clac "4 3 9 . * , +"`