/* * %CopyrightBegin% * * Copyright Ericsson AB 2001-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: * ei_format to build binary format terms a bit like printf */ #ifdef VXWORKS #include #endif #include #include #include #include #include #ifdef VRTX #define __READY_EXTENSIONS__ #include #endif #include "eidef.h" #include "ei_malloc.h" #include "ei_format.h" /* * To avoid problems we read the variable number of arguments to an * array of unions. */ union arg { char c; char* s; long l; unsigned long u; double d; erlang_pid* pid; }; static int eiformat(const char** s, union arg** args, ei_x_buff* x); /* forwards of parse functions */ static int pformat(const char** fmt, union arg**, ei_x_buff* x); static int plist(const char** fmt, union arg**, ei_x_buff* x, int size); static int ptuple(const char** fmt, union arg**, ei_x_buff* x, int size); static int pquotedatom(const char** fmt, ei_x_buff* x); static int pdigit(const char** fmt, ei_x_buff* x); static int patom(const char** fmt, ei_x_buff* x); static int pstring(const char** fmt, ei_x_buff* x); /* format a string into an ei_x_buff, except the version token */ static int eiformat(const char** fmt, union arg** args, ei_x_buff* x) { const char* p = *fmt; int res; ei_x_buff x2; while (isspace((int)*p)) ++p; switch (*p) { case '~': res = pformat(&p, args, x); break; case '[': res = ei_x_new(&x2); if (res >= 0) res = plist(&p, args, &x2, 0); if (res > 0) res = ei_x_encode_list_header(x, res); if (res >= 0) res = ei_x_append(x, &x2); ei_x_free(&x2); break; case '{': res = ei_x_new(&x2); if (res >= 0) res = ptuple(&p, args, &x2, 0); if (res >= 0) res = ei_x_encode_tuple_header(x, res); if (res >= 0) res = ei_x_append(x, &x2); ei_x_free(&x2); break; case '"': res = pstring(&p, x); break; case '\'': res = pquotedatom(&p, x); break; default: if (isdigit((int)*p)) res = pdigit(&p, x); else if ((*p == '-' || *p == '+') && isdigit((int)*(p+1))) res = pdigit(&p, x); else if (islower((int)*p)) res = patom(&p, x); else res = -1; break; /* Variables */ } *fmt = p; return res; } static int patom(const char** fmt, ei_x_buff* x) { const char* start = *fmt; char c; int len; for (;;) { c = *(*fmt)++; if (isalnum((int) c) || (c == '_') || (c == '@')) continue; else break; } --(*fmt); len = *fmt - start; /* FIXME why truncate atom name and not fail?! */ if (len >= MAXATOMLEN) len = MAXATOMLEN-1; return ei_x_encode_atom_len(x, start, len); } /* Check if integer or float */ static int pdigit(const char** fmt, ei_x_buff* x) { const char* start = *fmt; char c; int dotp=0; double d; long l; if (**fmt == '-' || **fmt == '+') (*fmt)++; for (;;) { c = *(*fmt)++; if (isdigit((int)c)) continue; else if (!dotp && (c == '.')) { dotp = 1; continue; } else break; } --(*fmt); if (dotp) { sscanf(start, "%lf", &d); return ei_x_encode_double(x, d); } else { sscanf(start, "%ld", &l); return ei_x_encode_long(x, l); } } /* "string" */ static int pstring(const char** fmt, ei_x_buff* x) { const char* start = ++(*fmt); /* skip first quote */ char c; int res; for (;;) { c = *(*fmt)++; if (c == '\0') return -1; if (c == '"') { if (*((*fmt)-1) == '\\') continue; else break; } else continue; } res = ei_x_encode_string_len(x, start, *fmt - start - 1); return res; } /* 'atom' */ static int pquotedatom(const char** fmt, ei_x_buff* x) { const char* start = ++(*fmt); /* skip first quote */ char c; int res; for (;;) { c = *(*fmt)++; if (c == 0) return -1; if (c == '\'') { if (*((*fmt)-1) == '\\') continue; else break; } else continue; } res = ei_x_encode_atom_len(x, start, *fmt - start - 1); return res; } /* * The format letters are: * a - An atom * c - A character * s - A string * i - An integer * l - A long integer * u - An unsigned long integer * f - A float * d - A double float * p - An Erlang PID */ static int pformat(const char** fmt, union arg** args, ei_x_buff* x) { int res = 0; ++(*fmt); /* skip tilde */ switch (*(*fmt)++) { case 'a': res = ei_x_encode_atom(x, (*args)->s); (*args)++; break; case 'c': res = ei_x_encode_char(x, (*args)->c); (*args)++; break; case 's': res = ei_x_encode_string(x, (*args)->s); (*args)++; break; case 'i': res = ei_x_encode_long(x, (*args)->l); (*args)++; break; case 'l': res = ei_x_encode_long(x, (*args)->l); (*args)++; break; case 'u': res = ei_x_encode_ulong(x, (*args)->u); (*args)++; break; case 'f': /* float is expanded to double (C calling conventions) */ case 'd': res = ei_x_encode_double(x, (*args)->d); (*args)++; break; case 'p': res = ei_x_encode_pid(x, (*args)->pid); (*args)++; break; default: res = -1; break; } return res; } /* encode a tuple */ static int ptuple(const char** fmt, union arg** args, ei_x_buff* x, int size) { int res = 0; const char* p = *fmt; char after = *p++; if (after == '}') { *fmt = p; return size; } while (isspace((int)*p)) ++p; switch (*p++) { case '}': if (after == ',') res = -1; else res = size; break; case ',': if (after == ',' || after == '{') res = -1; else res = ptuple(&p, args, x, size); break; default: --p; res = eiformat(&p, args, x); if (res >= 0) res = ptuple(&p, args, x, size + 1); break; /* Variables */ } *fmt = p; return res; } /* encode a list */ static int plist(const char** fmt, union arg** args, ei_x_buff* x, int size) { int res = 0; const char* p = *fmt; char after = *p++; if (after == ']') --p; while (isspace((int)*p)) ++p; switch (*p++) { case ']': if (after == ',') res = -1; else { if (after != '|') ei_x_encode_empty_list(x); res = size; } break; case '|': if (after == '|' || after == ',') res = -1; else res = plist(&p, args, x, size); break; case ',': if (after == '|' || after == ',') res = -1; else res = plist(&p, args, x, size); break; default: --p; res = eiformat(&p, args, x); ++size; if (res >= 0) { if (after == '|') { while (isspace((int)*p)) ++p; if (*p != ']') res = -1; } else res = plist(&p, args, x, size); } break; /* Variables */ } *fmt = p; return res; } static int read_args(const char* fmt, va_list ap, union arg **argp) { const char* p = fmt; int arg_count = 0; union arg* args; int i = 0; /* Count the number of format strings. Assume null terminated string. */ *argp = NULL; while (*p) if (*p++ == '~') arg_count++; if (!arg_count) { return 0; } /* Allocate space for the arguments */ args = (union arg*)ei_malloc(arg_count * sizeof(union arg)); if (!args) return -1; p = fmt; /* Start again and fill array */ while (*p) { if (*p++ == '~') { if (!*p) { ei_free(args); return -1; /* Error, string not complete */ } switch (*p++) { case 'c': args[i++].c = (char) va_arg(ap, int); break; case 'a': case 's': args[i++].s = va_arg(ap, char*); break; case 'i': #ifdef EI_64BIT args[i++].l = (long) va_arg(ap, int); break; #endif case 'l': args[i++].l = va_arg(ap, long); break; case 'u': args[i++].u = va_arg(ap, unsigned long); break; case 'f': /* float is expanded to double (C calling conventions) */ case 'd': args[i++].d = va_arg(ap, double); break; case 'p': args[i++].pid = va_arg(ap, erlang_pid*); break; default: ei_free(args); /* Invalid specifier */ return -1; } } } *argp = args; return 0; } int ei_x_format(ei_x_buff* x, const char* fmt, ... ) { va_list ap; union arg* args; union arg* saved_args; int res; res = ei_x_encode_version(x); if (res < 0) return res; va_start(ap, fmt); res = read_args(fmt,ap,&args); saved_args = args; va_end(ap); if (res < 0) { return -1; } res = eiformat(&fmt, &args, x); ei_free(saved_args); return res; } int ei_x_format_wo_ver(ei_x_buff* x, const char* fmt, ... ) { va_list ap; union arg* args; union arg* saved_args; int res; va_start(ap, fmt); res = read_args(fmt,ap,&args); saved_args = args; va_end(ap); if (res < 0) { return -1; } res = eiformat(&fmt, &args, x); ei_free(saved_args); return res; }