| /* |
| * parse.y: the parser for GNU units, a program for units conversion |
| * Copyright (C) 1999-2002, 2007, 2009, 2014, 2017-2018, 2020, 2024 |
| * Free Software Foundation, Inc |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| * This program was written by Adrian Mariano (adrianm@gnu.org) |
| */ |
| |
| |
| %{ |
| #include<stdio.h> |
| #include<float.h> |
| #include "units.h" |
| |
| struct commtype { |
| int location; |
| const char *data; |
| struct unittype *result; |
| int errorcode; |
| }; |
| |
| static int err; /* value used by parser to store return values */ |
| |
| /* |
| The CHECK macro aborts parse if an error has occurred. It optionally |
| destroys a variable. Call with CHECK(0) if no variables need destruction |
| on error. |
| */ |
| |
| #define CHECK(var) if (err) { comm->errorcode=err; \ |
| if (var) destroyunit(var); \ |
| YYABORT; } |
| |
| int yylex(); |
| void yyerror(struct commtype *comm, char *); |
| |
| #define MAXMEM 100 |
| int unitcount=0; /* Counts the number of units allocated by the parser */ |
| |
| struct function { |
| char *name; |
| double (*func)(double); |
| int type; |
| }; |
| |
| #define DIMENSIONLESS 0 |
| #define ANGLEIN 1 |
| #define ANGLEOUT 2 |
| #define NATURAL 3 |
| |
| struct unittype * |
| getnewunit() |
| { |
| struct unittype *unit; |
| |
| if (unitcount>=MAXMEM) |
| return 0; |
| unit = (struct unittype *) |
| mymalloc(sizeof(struct unittype),"(getnewunit)"); |
| if (!unit) |
| return 0; |
| initializeunit(unit); |
| unitcount++; |
| return unit; |
| } |
| |
| |
| void |
| destroyunit(struct unittype *unit) |
| { |
| freeunit(unit); |
| free(unit); |
| unitcount--; |
| } |
| |
| |
| struct unittype * |
| makenumunit(double num,int *myerr) |
| { |
| struct unittype *ret; |
| ret=getnewunit(); |
| if (!ret){ |
| *myerr = E_PARSEMEM; |
| return 0; |
| } |
| ret->factor = num; |
| *myerr = 0; |
| return ret; |
| } |
| |
| int |
| logunit(struct unittype *theunit, int base) |
| { |
| if ((err=unit2num(theunit))) |
| return err; |
| if (base==2) |
| theunit->factor = log2(theunit->factor); |
| else if (base==10) |
| theunit->factor = log10(theunit->factor); |
| else |
| theunit->factor = log(theunit->factor)/log((double)base); |
| if (errno) |
| return E_FUNC; |
| return 0; |
| } |
| |
| int |
| funcunit(struct unittype *theunit, struct function const *fun) |
| { |
| struct unittype angleunit; |
| if (fun->type==ANGLEIN){ |
| err=unit2num(theunit); |
| if (err==E_NOTANUMBER){ |
| initializeunit(&angleunit); |
| angleunit.denominator[0] = dupstr("radian"); |
| angleunit.denominator[1] = 0; |
| err = multunit(theunit, &angleunit); |
| freeunit(&angleunit); |
| if (!err) |
| err = unit2num(theunit); |
| } |
| if (err) |
| return err; |
| } else if (fun->type==ANGLEOUT || fun->type == DIMENSIONLESS || fun->type == NATURAL) { |
| if ((err=unit2num(theunit))) |
| return err; |
| if (fun->type==NATURAL && (theunit->factor<0 || trunc(theunit->factor)!=theunit->factor)) |
| return E_NOTINDOMAIN; |
| } else |
| return E_BADFUNCTYPE; |
| errno = 0; |
| theunit->factor = (*(fun->func))(theunit->factor); |
| if (errno) |
| return E_FUNC; |
| if (fun->type==ANGLEOUT) { |
| theunit->numerator[0] = dupstr("radian"); |
| theunit->numerator[1] = 0; |
| } |
| return 0; |
| } |
| |
| |
| %} |
| |
| %parse-param {struct commtype *comm} |
| %lex-param {struct commtype *comm} |
| %define api.pure full |
| %define api.prefix {units} |
| |
| %union { |
| double number; |
| int integer; |
| struct unittype *unit; |
| struct function *realfunc; |
| struct func *unitfunc; |
| } |
| |
| %token <number> REAL |
| %token <unit> UNIT |
| %token <realfunc> REALFUNC |
| %token <integer> LOG |
| %token <unitfunc> UNITFUNC |
| %token <integer> EXPONENT |
| %token <integer> MULTIPLY |
| %token <integer> MULTSTAR |
| %token <integer> DIVIDE |
| %token <integer> NUMDIV |
| %token <integer> SQRT |
| %token <integer> CUBEROOT |
| %token <integer> MULTMINUS |
| %token <integer> EOL |
| %token <integer> FUNCINV |
| %token <integer> MEMERROR |
| %token <integer> BADNUMBER |
| %token <integer> NUMOVERFLOW |
| %token <integer> NUMUNDERFLOW |
| %token <integer> UNITEND |
| %token <integer> LASTUNSET |
| |
| %type <number> numexpr |
| %type <unit> expr |
| %type <unit> list |
| %type <unit> pexpr |
| %type <unit> unitexpr |
| %type <unit> divlist |
| |
| %destructor { destroyunit($$);} <unit> |
| |
| %left ADD MINUS |
| %left UNARY |
| %left DIVIDE MULTSTAR |
| %left MULTIPLY MULTMINUS |
| %nonassoc '(' SQRT CUBEROOT REALFUNC LOG UNIT REAL UNITFUNC FUNCINV MEMERROR BADNUMBER NUMOVERFLOW NUMUNDERFLOW UNITEND LASTUNSET |
| %right EXPONENT |
| %left NUMDIV |
| |
| |
| %% |
| input: EOL { comm->result = makenumunit(1,&err); CHECK(0); |
| comm->errorcode = 0; YYACCEPT; } |
| | unitexpr EOL { comm->result = $1; comm->errorcode = 0; YYACCEPT; } |
| | error { YYABORT; } |
| ; |
| |
| unitexpr: expr { $$ = $1;} |
| | divlist { $$ = $1;} |
| ; |
| |
| divlist: DIVIDE list { invertunit($2); $$=$2;} |
| | divlist divlist %prec MULTIPLY {err = multunit($1,$2); destroyunit($2); |
| CHECK($1);$$=$1;} |
| ; |
| |
| expr: list { $$ = $1; } |
| | MULTMINUS list %prec UNARY { $$ = $2; $$->factor *= -1; } |
| | MINUS list %prec UNARY { $$ = $2; $$->factor *= -1; } |
| | expr ADD expr { err = addunit($1,$3); destroyunit($3); |
| CHECK($1);$$=$1;} |
| | expr MINUS expr { $3->factor *= -1; |
| err = addunit($1,$3); destroyunit($3); |
| CHECK($1);$$=$1;} |
| | expr DIVIDE expr { err = divunit($1, $3); destroyunit($3); |
| CHECK($1);$$=$1;} |
| | expr MULTIPLY expr { err = multunit($1,$3); destroyunit($3); |
| CHECK($1);$$=$1;} |
| | expr MULTSTAR expr { err = multunit($1,$3); destroyunit($3); |
| CHECK($1);$$=$1;} |
| ; |
| |
| numexpr: REAL { $$ = $1; } |
| | numexpr NUMDIV numexpr { $$ = $1 / $3; } |
| ; |
| |
| pexpr: '(' expr ')' { $$ = $2; } |
| ; |
| |
| /* list is a list of units, possibly raised to powers, to be multiplied |
| together. */ |
| |
| list: numexpr { $$ = makenumunit($1,&err); CHECK(0);} |
| | UNIT { $$ = $1; } |
| | list EXPONENT list { err = unitpower($1,$3);destroyunit($3); |
| CHECK($1);$$=$1;} |
| | list MULTMINUS list { err = multunit($1,$3); destroyunit($3); |
| CHECK($1);$$=$1;} |
| | list list %prec MULTIPLY { err = multunit($1,$2); destroyunit($2); |
| CHECK($1);$$=$1;} |
| | pexpr { $$=$1; } |
| | SQRT pexpr { err = rootunit($2,2); CHECK($2); $$=$2;} |
| | CUBEROOT pexpr { err = rootunit($2,3); CHECK($2); $$=$2;} |
| | REALFUNC pexpr { err = funcunit($2,$1);CHECK($2); $$=$2;} |
| | LOG pexpr { err = logunit($2,$1); CHECK($2); $$=$2;} |
| | UNITFUNC pexpr { err = evalfunc($2,$1,0,0); CHECK($2);$$=$2;} |
| | FUNCINV UNITFUNC pexpr { err = evalfunc($3,$2,1,0); CHECK($3);$$=$3;} |
| | list EXPONENT MULTMINUS list %prec EXPONENT |
| { $4->factor *= -1; err = unitpower($1,$4); |
| destroyunit($4);CHECK($1);$$=$1;} |
| | list EXPONENT MINUS list %prec EXPONENT |
| { $4->factor *= -1; err = unitpower($1,$4); |
| destroyunit($4);CHECK($1);$$=$1;} |
| | BADNUMBER { err = E_BADNUM; CHECK(0); } |
| | NUMOVERFLOW { err = E_OVERFLOW; CHECK(0); } |
| | NUMUNDERFLOW { err = E_UNDERFLOW;CHECK(0); } |
| | MEMERROR { err = E_PARSEMEM; CHECK(0); } |
| | UNITEND { err = E_UNITEND; CHECK(0); } |
| | LASTUNSET { err = E_LASTUNSET;CHECK(0); } |
| | FUNCINV UNIT { err = E_NOTAFUNC; CHECK($2);} |
| ; |
| |
| %% |
| |
| double |
| factorial(double x) |
| { |
| return tgamma(x+1); |
| } |
| |
| struct function |
| realfunctions[] = { {"sin", sin, ANGLEIN}, |
| {"cos", cos, ANGLEIN}, |
| {"tan", tan, ANGLEIN}, |
| {"ln", log, DIMENSIONLESS}, |
| {"log", log10, DIMENSIONLESS}, |
| {"exp", exp, DIMENSIONLESS}, |
| {"acos", acos, ANGLEOUT}, |
| {"atan", atan, ANGLEOUT}, |
| {"asin", asin, ANGLEOUT}, |
| {"sinh", sinh, DIMENSIONLESS}, |
| {"cosh", cosh, DIMENSIONLESS}, |
| {"tanh", tanh, DIMENSIONLESS}, |
| {"asinh", asinh, DIMENSIONLESS}, |
| {"acosh", acosh, DIMENSIONLESS}, |
| {"atanh", atanh, DIMENSIONLESS}, |
| {"round", round, DIMENSIONLESS}, |
| {"floor", floor, DIMENSIONLESS}, |
| {"ceil", ceil, DIMENSIONLESS}, |
| {"erf", erf, DIMENSIONLESS}, |
| {"erfc", erfc, DIMENSIONLESS}, |
| {"Gamma", tgamma, DIMENSIONLESS}, |
| {"lnGamma", lgamma, DIMENSIONLESS}, |
| {"factorial", factorial, NATURAL}, |
| {0, 0, 0}}; |
| |
| struct { |
| char op; |
| int value; |
| } optable[] = { {'*', MULTIPLY}, |
| {'/', DIVIDE}, |
| {'|', NUMDIV}, |
| {'+', ADD}, |
| {'(', '('}, |
| {')', ')'}, |
| {'^', EXPONENT}, |
| {'~', FUNCINV}, |
| {0, 0}}; |
| |
| struct { |
| char *name; |
| int value; |
| } strtable[] = { {"sqrt", SQRT}, |
| {"cuberoot", CUBEROOT}, |
| {"per" , DIVIDE}, |
| {0, 0}}; |
| |
| #define LASTUNIT '_' /* Last unit symbol */ |
| |
| |
| int yylex(YYSTYPE *lvalp, struct commtype *comm) |
| { |
| int length, count; |
| struct unittype *output; |
| const char *inptr; |
| char *name; |
| |
| char *nonunitchars = "~;+-*/|\t\n^ ()"; /* Chars not allowed in unit name --- also defined in units.c */ |
| char *nonunitstart = ".,"; /* Can't start a unit */ |
| char *nonunitend = ".,_"; /* Can't end a unit */ |
| char *number_start = ".,0123456789"; /* Can be first char of a number */ |
| |
| if (comm->location==-1) return 0; |
| inptr = comm->data + comm->location; /* Point to start of data */ |
| |
| /* Skip spaces */ |
| while(*inptr==' ') inptr++, comm->location++; |
| |
| if (*inptr==0) { |
| comm->location = -1; |
| return EOL; /* Return failure if string has ended */ |
| } |
| |
| /* Check for **, an exponent operator. */ |
| |
| if (0==strncmp("**",inptr,2)){ |
| comm->location += 2; |
| return EXPONENT; |
| } |
| |
| /* Check for '-' and '*' which get special handling */ |
| |
| if (*inptr=='-'){ |
| comm->location++; |
| if (parserflags.minusminus) |
| return MINUS; |
| return MULTMINUS; |
| } |
| |
| if (*inptr=='*'){ |
| comm->location++; |
| if (parserflags.oldstar) |
| return MULTIPLY; |
| return MULTSTAR; |
| } |
| |
| /* Look for single character ops */ |
| |
| for(count=0; optable[count].op; count++){ |
| if (*inptr==optable[count].op) { |
| comm->location++; |
| return optable[count].value; |
| } |
| } |
| |
| /* Look for numbers */ |
| |
| if (strchr(number_start,*inptr)){ /* prevent "nan" from being recognized */ |
| char *endloc; |
| errno=0; |
| lvalp->number = strtod(inptr, &endloc); |
| if (inptr != endloc) { |
| comm->location += (endloc-inptr); |
| if (*endloc && strchr(number_start,*endloc)) |
| return BADNUMBER; |
| else if (errno){ |
| errno=0; |
| if (fabs(lvalp->number)==HUGE_VAL) return NUMOVERFLOW; |
| else return NUMUNDERFLOW; |
| } |
| else |
| return REAL; |
| } |
| } |
| |
| /* Look for a word (function name or unit name) */ |
| |
| length = strcspn(inptr,nonunitchars); |
| |
| if (!length){ /* Next char is not a valid unit char */ |
| comm->location++; |
| return 0; |
| } |
| |
| /* Check for the "last unit" symbol, with possible exponent */ |
| |
| if (*inptr == LASTUNIT && |
| (length==1 || length==2 && strchr("23456789",inptr[1]))){ |
| comm->location++; |
| if (!lastunitset) |
| return LASTUNSET; |
| output = getnewunit(); |
| if (!output) |
| return MEMERROR; |
| unitcopy(output, &lastunit); |
| if (length==2){ |
| expunit(output, inptr[1]-'0'); |
| comm->location++; |
| } |
| lvalp->unit = output; |
| return UNIT; |
| } |
| |
| /* Check that unit name doesn't start or end with forbidden chars */ |
| if (strchr(nonunitstart,*inptr)){ |
| comm->location++; |
| return 0; |
| } |
| if (strchr(nonunitend, inptr[length-1])){ |
| comm->location+=length; |
| return 0; |
| } |
| |
| name = dupnstr(inptr, length); |
| |
| /* Look for string operators */ |
| |
| for(count=0;strtable[count].name;count++){ |
| if (!strcmp(name,strtable[count].name)){ |
| free(name); |
| comm->location += length; |
| return strtable[count].value; |
| } |
| } |
| |
| /* Look for real function names */ |
| |
| for(count=0;realfunctions[count].name;count++){ |
| if (!strcmp(name,realfunctions[count].name)){ |
| lvalp->realfunc = realfunctions+count; |
| comm->location += length; |
| free(name); |
| return REALFUNC; |
| } |
| } |
| |
| /* Check for arbitrary base log */ |
| |
| if (!strncmp(name, "log",3)){ |
| count = strspn(name+3,"1234567890"); |
| if (count+3 == strlen(name)){ |
| lvalp->integer=atoi(name+3); |
| if (lvalp->integer>1){ /* Log base must be larger than 1 */ |
| comm->location += length; |
| free(name); |
| return LOG; |
| } |
| } |
| } |
| |
| /* Look for function parameter */ |
| |
| if (function_parameter && !strcmp(name,function_parameter)){ |
| free(name); |
| output = getnewunit(); |
| if (!output) |
| return MEMERROR; |
| unitcopy(output, parameter_value); |
| lvalp->unit = output; |
| comm->location += length; |
| return UNIT; |
| } |
| |
| /* Look for user defined function */ |
| |
| lvalp->unitfunc = fnlookup(name); |
| if (lvalp->unitfunc){ |
| comm->location += length; |
| free(name); |
| return UNITFUNC; |
| } |
| |
| /* Didn't find a special string, so treat it as unit name */ |
| |
| comm->location+=length; |
| if (strchr("23456789",inptr[length-1]) && !hassubscript(name)) { |
| /* ends with digit but not a subscript, so do exponent handling like m3 */ |
| count = name[length-1] - '0'; |
| length--; |
| if (strchr(number_start, name[length-1])){ |
| free(name); |
| return UNITEND; |
| } |
| } else count=1; |
| |
| free(name); |
| |
| output = getnewunit(); |
| if (!output) |
| return MEMERROR; |
| output->numerator[count--]=0; |
| for(;count>=0;count--) |
| output->numerator[count] = dupnstr(inptr, length); |
| lvalp->unit=output; |
| return UNIT; |
| } |
| |
| |
| void yyerror(struct commtype *comm, char *s){} |
| |
| |
| int |
| parseunit(struct unittype *output, char const *input,char **errstr,int *errloc) |
| { |
| struct commtype comm; |
| int saveunitcount; |
| |
| saveunitcount = unitcount; |
| initializeunit(output); |
| comm.result = 0; |
| comm.location = 0; |
| comm.data = input; |
| comm.errorcode = E_PARSE; /* Assume parse error */ |
| errno=0; |
| /* errno should only be set in the case of invalid function arguments */ |
| if (yyparse(&comm) || errno){ |
| if (comm.location==-1) |
| comm.location = strlen(input); |
| if (errstr){ |
| if (comm.errorcode==E_FUNC || errno) |
| *errstr = strerror(errno); |
| else |
| *errstr=errormsg[comm.errorcode]; |
| } |
| if (errloc) |
| *errloc = comm.location; |
| if (unitcount!=saveunitcount) |
| fprintf(stderr,"units: Parser leaked memory with error: %d in %d out\n", |
| saveunitcount, unitcount); |
| return comm.errorcode; |
| } else { |
| if (errstr) |
| *errstr = 0; |
| multunit(output,comm.result); |
| destroyunit(comm.result); |
| if (unitcount!=saveunitcount) |
| fprintf(stderr,"units: Parser leaked memory without error: %d in %d out\n", |
| saveunitcount, unitcount); |
| return 0; |
| } |
| } |
| |
| |