/* * %CopyrightBegin% * * Copyright Ericsson AB 1996-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ /* * Function: Provides two primitives: erl_format to build * Erlang terms in an easy way, and erl_match to perform * pattern match similar to what is done in Erlang. * */ #include "eidef.h" #include #include #include #include #include #ifdef VRTX #define __READY_EXTENSIONS__ #include #endif #include "erl_interface.h" #include "erl_eterm.h" #include "erl_malloc.h" #include "erl_error.h" #include "erl_internal.h" #define ERL_TRUE 1 #define ERL_FALSE 0 #define ERL_OK 0 #define ERL_FORMAT_ERROR -1 #define ERL_MAX_ENTRIES 255 /* Max entries in a tuple/list term */ #define ERL_MAX_NAME_LENGTH 255 /* Max length of variable names */ #define PRINT(t) \ { \ print_term(stderr,t); \ fprintf(stderr,"\n"); \ } typedef struct lvar { ETERM *var; struct lvar *next; } lvar; /* Forward */ static ETERM *eformat(char**, va_list*); static int ematch(ETERM*, ETERM*); /* FIXME not thread safe */ struct _ef { lvar *chain; /* Chain of local variables */ lvar *idle; /* Idle list of lvar's */ } ef; /* Find local variable in term. */ static ETERM *find_lvar(char *name) { lvar *tmp=ef.chain; while (tmp != NULL) { if (strcmp(tmp->var->uval.vval.name,name) == 0) return tmp->var->uval.vval.v; tmp = tmp->next; } return (ETERM *) NULL; } /* find_lvar */ static void lvar_free(lvar *lv) { lvar *tmp=ef.chain; /* Link in the chain into the idle list */ if (ef.idle == NULL) ef.idle = lv; else { tmp = ef.idle; while (tmp->next != NULL) tmp = tmp->next; tmp->next = lv; } /* Clear out the variable information */ tmp = lv; while (tmp != NULL) { tmp->var = (ETERM *) NULL; tmp = tmp->next; } } /* lvar_free */ static lvar *lvar_alloc(void) { lvar *tmp; if ((tmp = ef.idle) == NULL) { tmp = (lvar *) erl_malloc(sizeof(lvar)); } else { tmp = ef.idle; ef.idle = tmp->next; } return tmp; } /* lvar_alloc */ static void undo_bindings(void) { lvar *tmp=ef.chain; while (tmp != NULL) { erl_free_term(tmp->var->uval.vval.v); tmp->var->uval.vval.v = (ETERM *) NULL; tmp = tmp->next; } } /* undo_bindings */ static void release_chain(void) { lvar_free(ef.chain); ef.chain = (lvar *) NULL; } /* release_chain */ static void add_lvar(ETERM *t) { lvar *lv; lv = lvar_alloc(); lv->var = t; lv->next = ef.chain; ef.chain = lv; } /* add_lvar */ static char *pvariable(char **fmt, char *buf) { char *start=*fmt; char c; int len; while (1) { c = *(*fmt)++; if (isalnum((int) c) || (c == '_')) continue; else break; } (*fmt)--; len = *fmt - start; memcpy(buf, start, len); buf[len] = 0; return buf; } /* pvariable */ static char *patom(char **fmt, char *buf) { char *start=*fmt; char c; int len; while (1) { c = *(*fmt)++; if (isalnum((int) c) || (c == '_') || (c == '@')) continue; else break; } (*fmt)--; len = *fmt - start; memcpy(buf, start, len); buf[len] = 0; return buf; } /* patom */ /* Check if integer or float */ static char *pdigit(char **fmt, char *buf) { char *start=*fmt; char c; int len,dotp=0; while (1) { c = *(*fmt)++; if (isdigit((int) c)) continue; else if (!dotp && (c == '.')) { dotp = 1; continue; } else break; } (*fmt)--; len = *fmt - start; memcpy(buf, start, len); buf[len] = 0; return buf; } /* pdigit */ static char *pstring(char **fmt, char *buf) { char *start=++(*fmt); /* skip first quote */ char c; int len; while (1) { c = *(*fmt)++; if (c == '"') { if (*((*fmt)-1) == '\\') continue; else break; } else continue; } len = *fmt - 1 - start; /* skip last quote */ memcpy(buf, start, len); buf[len] = 0; return buf; } /* pstring */ static char *pquotedatom(char **fmt, char *buf) { char *start=++(*fmt); /* skip first quote */ char c; int len; while (1) { c = *(*fmt)++; if (c == '\'') { if (*((*fmt)-1) == '\\') continue; else break; } else continue; } len = *fmt - 1 - start; /* skip last quote */ memcpy(buf, start, len); buf[len] = 0; return buf; } /* pquotedatom */ /* * The format letters are: * w - Any Erlang term * a - An Atom * b - A Binary * s - A String * i - An Integer * f - A Float (double) */ static int pformat(char **fmt, va_list *pap, ETERM *v[], int size) { int rc=ERL_OK; /* this next section hacked to remove the va_arg calls */ switch (*(*fmt)++) { case 'w': v[size] = va_arg(*pap, ETERM*); ERL_COUNT(v[size])++; break; case 'a': v[size] = erl_mk_atom(va_arg(*pap, char *)); break; case 's': v[size] = erl_mk_string(va_arg(*pap, char *)); break; case 'i': v[size] = erl_mk_int(va_arg(*pap, int)); break; case 'f': v[size] = erl_mk_float(va_arg(*pap, double)); break; case 'b': { char *sarg = va_arg(*pap, char *); v[size] = erl_mk_binary(sarg, strlen(sarg)); break; } default: rc = ERL_FORMAT_ERROR; break; } return rc; } /* pformat */ static int ptuple(char **fmt, va_list *pap, ETERM *v[], int size) { int res=ERL_FORMAT_ERROR; switch (*(*fmt)++) { case '}': res = size; break; case ',': res = ptuple(fmt, pap, v, size); break; case '~': if (pformat(fmt, pap, v, size) == ERL_OK) res = ptuple(fmt, pap, v, ++size); else erl_err_msg("ptuple(1): Wrong format sequence !"); break; case ' ': return ptuple(fmt, pap, v, size); break; default: { (*fmt)--; if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL) res = ptuple(fmt, pap, v, size); break; /* if (isupper(**fmt)) { v[size++] = erl_mk_var(pvariable(fmt, wbuf)); res = ptuple(fmt, pap, v, size); } else if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL) res = ptuple(fmt, pap, v, size); break; */ } } /* switch */ return res; } /* ptuple */ static int plist(char **fmt, va_list *pap, ETERM *v[], int size) { int res=ERL_FORMAT_ERROR; switch (*(*fmt)++) { case ']': res = size; break; case ',': res = plist(fmt, pap, v, size); break; case '~': if (pformat(fmt, pap, v, size) == ERL_OK) res = plist(fmt, pap, v, ++size); else erl_err_msg("plist(1): Wrong format sequence !"); break; case ' ': return plist(fmt, pap, v, size); break; default: { (*fmt)--; if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL) res = plist(fmt, pap, v, size); break; /* if (isupper(**fmt)) { v[size++] = erl_mk_var(pvariable(fmt, wbuf)); res = plist(fmt, pap, v, size); } else if ((v[size++] = eformat(fmt, pap)) != (ETERM *) NULL) res = plist(fmt, pap, v, size); break; */ } } /* switch */ return res; } /* plist */ static ETERM *eformat(char **fmt, va_list *pap) { int size; ETERM *v[ERL_MAX_ENTRIES],*ep; switch (*(*fmt)++) { case '{': if ((size = ptuple(fmt, pap , v, 0)) != ERL_FORMAT_ERROR) { ep = erl_mk_tuple(v, size); erl_free_array(v, size); return ep; } else return (ETERM *) NULL; break; case '[': if (**fmt == ']') { (*fmt)++; return erl_mk_empty_list(); } else if ((size = plist(fmt, pap , v, 0)) != ERL_FORMAT_ERROR) { ep = erl_mk_list(v, size); erl_free_array(v, size); return ep; } else return (ETERM *) NULL; break; case '$': /* char-value? */ return erl_mk_int((int)(*(*fmt)++)); break; case '~': if (pformat(fmt, pap, v, 0) == ERL_OK) { ep = erl_copy_term(v[0]); erl_free_term(v[0]); return ep; } break; case ' ': return eformat(fmt, pap); break; /* handle negative numbers too... * case '-': * { * ETERM *tmp; * * tmp = eformat(fmt,pap); * if (ERL_IS_INTEGER(tmp)) ERL_INT_VALUE(tmp) = -(ERL_INT_VALUE(tmp)); * return tmp; * } * * * break; */ default: { char wbuf[BUFSIZ]; /* now local to this function for reentrancy */ (*fmt)--; if (islower((int)**fmt)) { /* atom ? */ char *atom=patom(fmt, wbuf); return erl_mk_atom(atom); } else if (isupper((int)**fmt) || (**fmt == '_')) { char *var=pvariable(fmt, wbuf); return erl_mk_var(var); } else if (isdigit((int)**fmt)) { /* integer/float ? */ char *digit=pdigit(fmt, wbuf); if (strchr(digit,(int) '.') == NULL) return erl_mk_int(atoi((const char *) digit)); else return erl_mk_float(atof((const char *) digit)); } else if (**fmt == '"') { /* string ? */ char *string=pstring(fmt, wbuf); return erl_mk_string(string); } else if (**fmt == '\'') { /* quoted atom ? */ char *qatom=pquotedatom(fmt, wbuf); return erl_mk_atom(qatom); } } break; } erl_err_msg(" Syntax error in eformat, char was: %c !", **fmt); return (ETERM *) NULL; } /* eformat */ ETERM *erl_format(char *fmt, ... ) { ETERM *res=NULL; va_list ap; va_start(ap, fmt); res = eformat(&fmt, &ap); va_end(ap); return res; } /* erl_format */ /* * Perform a pattern match between a pattern p and a term t. * As a side effect bind any unbound variables in p. * Return true or false. */ static int ematch(ETERM *p, ETERM *t) { unsigned int type_p; unsigned int type_t; ETERM *tmp; /* two NULLs are equal, one is not... */ if (!p && !t) return ERL_TRUE; if (!p || !t) return ERL_FALSE; /* * ASSERT(p != NULL); * ASSERT(t != NULL); */ type_p = ERL_TYPE(p); type_t = ERL_TYPE(t); if (type_t == ERL_VARIABLE) { if (t->uval.vval.v == NULL) return ERL_FALSE; /* Can't have an unbound variable here ! */ else t = t->uval.vval.v; } if (type_p != ERL_VARIABLE && type_p != type_t) return ERL_FALSE; switch (type_p) { case ERL_ATOM: { Erl_Atom_data* pa = &p->uval.aval.d; Erl_Atom_data* ta = &t->uval.aval.d; if (pa->utf8 && ta->utf8) { return pa->lenU == ta->lenU && memcmp(pa->utf8, ta->utf8, pa->lenU)==0; } else if (pa->latin1 && ta->latin1) { return pa->lenL == ta->lenL && memcmp(pa->latin1, ta->latin1, pa->lenL)==0; } else if (pa->latin1) { return cmp_latin1_vs_utf8(pa->latin1, pa->lenL, ta->utf8, ta->lenU)==0; } else { return cmp_latin1_vs_utf8(ta->latin1, ta->lenL, pa->utf8, pa->lenU)==0; } } case ERL_VARIABLE: if (strcmp(p->uval.vval.name, "_") == 0) /* anon. variable */ return ERL_TRUE; else if ((tmp = find_lvar(p->uval.vval.name)) != (ETERM *) NULL) { /* v points to NULL in cases like erl_format("{X,X}") for the second variable */ if (p->uval.vval.v == NULL) p->uval.vval.v = erl_copy_term(tmp); return ematch(p->uval.vval.v, t); } else { /* check if the variable is bound already */ if (p->uval.vval.v != NULL) { if (ematch(p->uval.vval.v, t) == ERL_TRUE ){ add_lvar(p); return ERL_TRUE; } else return ERL_FALSE; } else { p->uval.vval.v = erl_copy_term(t); add_lvar(p); return ERL_TRUE; } } break; case ERL_PID: if ((strcmp(ERL_PID_NODE(p), ERL_PID_NODE(t)) == 0) && (ERL_PID_NUMBER(p) == ERL_PID_NUMBER(t)) && (ERL_PID_SERIAL(p) == ERL_PID_SERIAL(t)) && (ERL_PID_CREATION(p) == ERL_PID_CREATION(t))) return ERL_TRUE; else return ERL_FALSE; break; case ERL_PORT: if ((strcmp(ERL_PORT_NODE(p), ERL_PORT_NODE(t)) == 0) && (ERL_PORT_NUMBER(p) == ERL_PORT_NUMBER(t)) && (ERL_PORT_CREATION(p) == ERL_PORT_CREATION(t))) return ERL_TRUE; else return ERL_FALSE; break; case ERL_REF: { int i, len; if (strcmp(ERL_REF_NODE(p), ERL_REF_NODE(t)) != 0 || ERL_REF_CREATION(p) != ERL_REF_CREATION(t)) return ERL_FALSE; /* FIXME: {len=1, n={42}} and {len=3, n={42, 17, 13}} tests equal. */ len = ERL_REF_LEN(p); if (len > ERL_REF_LEN(t)) len = ERL_REF_LEN(t); for (i = 0; i < len; i++) if (ERL_REF_NUMBERS(p)[i] != ERL_REF_NUMBERS(t)[i]) return ERL_FALSE; return ERL_TRUE; break; } case ERL_EMPTY_LIST: return ERL_TRUE; case ERL_LIST: while (ERL_IS_CONS(p) && ERL_IS_CONS(t)) { if (ematch(p->uval.lval.head, t->uval.lval.head) == ERL_FALSE) return ERL_FALSE; p = p->uval.lval.tail; t = t ->uval.lval.tail; } return ematch(p, t); case ERL_TUPLE: { int i; if (erl_size(p) != erl_size(t)) return ERL_FALSE; else { for(i=0; iuval.tval.elems[i],t->uval.tval.elems[i]) == ERL_FALSE) return ERL_FALSE; return ERL_TRUE; } } break; case ERL_BINARY: { int i; if ((i = p->uval.bval.size) != t->uval.bval.size) return ERL_FALSE; else return (memcmp(p->uval.bval.b,t->uval.bval.b,i)==0) ? ERL_TRUE : ERL_FALSE; } break; case ERL_INTEGER: return (p->uval.ival.i == t->uval.ival.i) ? ERL_TRUE : ERL_FALSE; break; case ERL_SMALL_BIG: case ERL_U_SMALL_BIG: /* This case can't happend since it is impossible * to create a bignum from the C code. */ return ERL_FALSE; break; case ERL_FLOAT: #if defined(VXWORKS) && CPU == PPC860 { return (erl_fp_compare((unsigned *)&(p->uval.fval.f), (unsigned *)&(t->uval.fval.f)) == 0) ? ERL_TRUE : ERL_FALSE; } #else return (p->uval.fval.f == t->uval.fval.f) ? ERL_TRUE : ERL_FALSE; #endif break; default: return ERL_FALSE; break; } /* erl_err_msg("ematch: Unknown type == %c\n", type_p); */ return ERL_FALSE; } /* ematch */ int erl_match(ETERM *p, ETERM *t) { int i; if ((i = ematch(p, t)) == ERL_FALSE) undo_bindings(); release_chain(); return i; } /* erl_match */