/* * SPL - The SPL Programming Language * Copyright (C) 2004, 2005 Clifford Wolf * * 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * builtins.c: Collection of some useful CLIB functions */ #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_READLINE_SUPPORT # include # include #endif #ifdef ENABLE_GETTEXT_SUPPORT # include # include #endif #include "spl.h" #include "compat.h" /** * This module contains the SPL 'standard' builtin functions. It does not * need to be loaded explicitely, most SPL runtimes load it automatically * for you. */ /** * Write the text passed as argument to standard ouput. Some SPL runtimes * replace this function with their own output function. * * If the output should not be UTF-8 encoded, the encoding must be specified * with a 2nd parameter. Valid encodings are the same as for the '#encoding' * compiler pragma (see 'SPL Language Reference' for a list). */ // builtin write(text, encoding) struct spl_node *spl_builtin_write(struct spl_task *task, void UNUSED(*data)) { char *text = spl_clib_get_string(task); char *encoding = spl_clib_get_string(task); if (*encoding) { char *old_text = text; text = spl_utf8_export(text, encoding); if ( !text ) { text = old_text; encoding = ""; } } printf("%s", text); fflush(stdout); if (*encoding) free(text); return 0; } /** * Read a line from the standard input. Some SPL runtimes replace this * function with their own input function. The read text (or undef on * end-of-file) is returned. * * The fist parameter can be used to specify a prompt to be displayed for * reading the line. * * If the output is not UTF-8 encoded, the encoding must be specified with a * 2nd parameter. Valid encodings are the same as for the '#encoding' compiler * pragma (see 'SPL Language Reference' for a list). * * If UTF-8 encoding is expected but the output fails to pass the UTF-8 test, * the output is assumed to be latin1 encoded. */ // builtin read(prompt, encoding) struct spl_node *spl_builtin_read(struct spl_task *task, void UNUSED(*data)) { char *prompt = spl_clib_get_string(task); char *encoding = spl_clib_get_string(task); char *input; #ifdef ENABLE_READLINE_SUPPORT if (isatty(0)) { static int did_run_using_history = 0; if (!did_run_using_history) { using_history(); did_run_using_history = 1; } input = readline(prompt); if (!input) { if (*prompt) { printf("\n"); fflush(stdout); } return 0; } add_history(input); } else #endif { printf("%s", prompt); fflush(stdout); char input_buffer[4096]; char *rc = fgets(input_buffer, 4096, stdin); if (!rc) { if (*prompt) { printf("\n"); fflush(stdout); } return 0; } input = strdup(input_buffer); } if (*encoding) { char *old_input = input; input = spl_utf8_import(old_input, encoding); if (!input) input = old_input; else free(old_input); } if (spl_utf8_check(input)) { char *old_input = input; input = spl_utf8_import(old_input, "latin_1"); free(old_input); /* this should never happen */ if (!input) input = strdup(""); } return SPL_NEW_STRING(input); } /** * Create a (unicode) charater from the number passed as argument. */ // builtin chr(number) struct spl_node *spl_builtin_chr(struct spl_task *task, void UNUSED(*data)) { int number = spl_clib_get_int(task); char text[5] = { 0, 0, 0, 0, 0 }; #define OUT(bits, pos) ((number >> pos) & (0xff >> (8-bits))) if ( number <= 0x00007F ) { text[0] = number; return SPL_NEW_STRING_DUP(text); } if ( number <= 0x0007FF ) { text[0] = 0xC0 | OUT(5, 6); text[1] = 0x80 | OUT(6, 0); return SPL_NEW_STRING_DUP(text); } if ( number <= 0x00FFFF ) { text[0] = 0xE0 | OUT(4, 12); text[1] = 0x80 | OUT(6, 6); text[2] = 0x80 | OUT(6, 0); return SPL_NEW_STRING_DUP(text); } if ( number <= 0x10FFFF ) { text[0] = 0xF0 | OUT(3, 18); text[1] = 0x80 | OUT(6, 12); text[2] = 0x80 | OUT(6, 6); text[3] = 0x80 | OUT(6, 0); return SPL_NEW_STRING_DUP(text); } #undef OUT /* Input is to big for beeing UTF-8 encoded. */ return 0; } /** * Return the unicode character number of the first character in the text. */ // builtin ord(text) struct spl_node *spl_builtin_ord(struct spl_task *task, void UNUSED(*data)) { char *t = spl_clib_get_string(task); #define IN(off, mask, value) ((t[off] & (mask)) == (value)) #define OUT(off, bits, pos) ((t[off] & (0xff >> (8-bits))) << pos) if (IN(0, 0x80, 0x00)) return SPL_NEW_INT(t[0]); if (IN(0, 0xE0, 0xC0) && IN(1, 0xC0, 0x80)) return SPL_NEW_INT(OUT(0, 5, 6) | OUT(1, 6, 0)); if (IN(0, 0xF0, 0xE0) && IN(1, 0xC0, 0x80) && IN(2, 0xC0, 0x80)) return SPL_NEW_INT(OUT(0, 4, 12) | OUT(1, 6, 6) | OUT(2, 6, 0)); if (IN(0, 0xF8, 0xF0) && IN(1, 0xC0, 0x80) && IN(2, 0xC0, 0x80) && IN(3, 0xC0, 0x80)) return SPL_NEW_INT(OUT(0, 3, 18) | OUT(1, 6, 12) | OUT(2, 6, 6) | OUT(3, 6, 0)); #undef OUT #undef IN /* Input is not UTF-8. This should not happen.. */ return 0; } /** * Convert a string holding a hexadezimal number to an integer value. A * leading "0x" or "0X" is ignored. Zero is returned if there is no * hexadezimal number in the string. */ // builtin hex(text) struct spl_node *spl_builtin_hex(struct spl_task *task, void UNUSED(*data)) { char *text = spl_clib_get_string(task); int ret = 0; while (*text == ' ' || *text == '\t') text++; if (!strncmp(text, "0x", 2) || !strncmp(text, "0X", 2)) text+=2; ret = strtol(text, 0, 16); return SPL_NEW_INT(ret); } /** * Convert a string holding an octal number to an integer value. A * leading "0o" or "0O" is ignored. Zero is returned if there is no * octal number in the string. */ // builtin oct(text) struct spl_node *spl_builtin_oct(struct spl_task *task, void UNUSED(*data)) { char *text = spl_clib_get_string(task); int ret = 0; while (*text == ' ' || *text == '\t') text++; if (!strncmp(text, "0o", 2) || !strncmp(text, "0O", 2)) text+=2; ret = strtol(text, 0, 8); return SPL_NEW_INT(ret); } /** * Convert a string holding a binary number to an integer value. A * leading "0b" or "0B" is ignored. Zero is returned if there is no * binary number in the string. */ // builtin bin(text) struct spl_node *spl_builtin_bin(struct spl_task *task, void UNUSED(*data)) { char *text = spl_clib_get_string(task); int ret = 0; while (*text == ' ' || *text == '\t') text++; if (!strncmp(text, "0b", 2) || !strncmp(text, "0B", 2)) text+=2; ret = strtol(text, 0, 2); return SPL_NEW_INT(ret); } /** * A C printf()-like format function. The formatted string is returned. * The following special sequences are supported: * * %% A '%' character * %s A string * %d A decimal integer * %x A hexadecimal integer (lowercase letters) * %X A hexadecimal integer (uppercase letters) * %o An octal integer * %b A binary integer * %f A floating point number * * The field width and precision format of printf() is also supported. But * currently there is no support for 'N$' argument specifications. */ // builtin fmt(text, @args) struct spl_node *spl_builtin_fmt(struct spl_task *task, void UNUSED(*data)) { static const char num_to_char_lowercase[] = "0123456789abcdef"; static const char num_to_char_uppercase[] = "0123456789ABCDEF"; char *format = spl_clib_get_string(task); struct spl_string *output = 0; while (*format) { if (*format != '%') { int text_len = strcspn(format, "%"); output = spl_string_new(SPL_STRING_NOAUTOGET, output, 0, my_strndup(format, text_len), 0); format += text_len; continue; } int format_len = strspn(++format, "-01233456789.*"); char *format_end = format + format_len; if (format[format_len] == 0) { output = spl_string_new(SPL_STRING_NOAUTOGET, output, 0, strdup("(format_string_error:EOT)"), 0); break; } int got_width = 0, width = 0; int got_prec = 0, prec = 0; int left_adjust = 0; while (*format == '-') { format++; left_adjust=1; } int zero_padding = 0; while (*format == '0') { format++; zero_padding=1; } { char *width_format = 0; if (*format == '*') { width_format = strdup(spl_clib_get_string(task)); format++; } else { int width_len = strspn(format, "-01233456789"); if (width_len) width_format = my_strndup(format, width_len); format += width_len; } if (width_format) { char *tmp = width_format; if (*tmp == '-') { tmp++; left_adjust=1; } if (*tmp) { width = atoi(tmp); got_width = 1; } } if (width_format) free(width_format); } if (*format == '.') { char *prec_format = 0; format++; if (*format == '*') { prec_format = strdup(spl_clib_get_string(task)); format++; } else { int prec_len = strspn(format, "-01233456789"); if (prec_len) prec_format = my_strndup(format, prec_len); format += prec_len; } if (prec_format && *prec_format) { prec = atoi(prec_format); got_prec = 1; } if (prec_format) free(prec_format); } char *insert = 0; int base = 10; const char *num_to_char = num_to_char_lowercase; switch (*format_end) { case '%': insert = strdup("%"); break; case 's': { char *arg = spl_clib_get_string(task); int arg_len = strlen(arg); int format_width = got_width ? width : arg_len; int format_prec = got_prec ? prec : arg_len; if (format_width > format_prec && !got_width) format_width = format_prec; if (left_adjust) format_width *= -1; my_asprintf(&insert, "%*.*s", format_width, format_prec, arg); break; } // a little bit ugly but still legal C .. if (0) { case 'x': base = 16; } if (0) { case 'X': base = 16; num_to_char = num_to_char_uppercase; } if (0) { case 'o': base = 8; } if (0) { case 'b': base = 2; } case 'd': { int arg = spl_clib_get_int(task); int myprec = 1; int mywidth = width; if (got_prec) myprec = prec; int buffer_len = (myprec < 64 ? 64 : myprec); if (got_width && buffer_len < width) buffer_len = width; char buffer_data[buffer_len + 8]; char *buffer = buffer_data + buffer_len + 7; int isneg = arg < 0; if (isneg) { arg *= -1; myprec--; mywidth--; } *buffer = 0; while (myprec > 0 || arg) { int rest = arg % base; arg = arg / base; *(--buffer) = num_to_char[rest]; myprec--; mywidth--; } if (zero_padding) while (mywidth > 0) { *(--buffer) = '0'; mywidth--; } if (isneg) *(--buffer) = '-'; if (got_width && left_adjust) { my_asprintf(&insert, "%-*s", width, buffer); break; } if (!zero_padding) while (mywidth > 0) { *(--buffer) = ' '; mywidth--; } insert = strdup(buffer); break; } case 'f': { double arg = spl_clib_get_float(task); int format_prec = got_prec ? prec : 6; if (got_width) { char *fmt_string = "%*.*f"; if (zero_padding) fmt_string = "%0*.*f"; if (left_adjust) fmt_string = "%-*.*f"; my_asprintf(&insert, fmt_string, width, format_prec, arg); } else my_asprintf(&insert, "%.*f", format_prec, arg); break; } default: spl_put(task->vm, spl_clib_get_node(task)); my_asprintf(&insert, "(format_string_error:%c)", *format_end); } if (insert) { output = spl_string_new(SPL_STRING_NOAUTOGET, output, 0, insert, 0); } format = format_end + 1; } return SPL_NEW_SPL_STRING(output); } static int rand_seeded = 0; /** * Returns a random integer number, larger then or equal to zero and smaller * then the 'max' argument. * * When the argument is 0 or missing, the return value is a floating point * number between 0 and 1. When the argument is -1, the return value is an * integer between 0 and RAND_MAX. */ // builtin rand(max) static struct spl_node *spl_builtin_rand(struct spl_task *task, void UNUSED(*data)) { int max = spl_clib_get_int(task); if (!rand_seeded) { unsigned int seed = (getpid() << 16) ^ time(0); int fd = open("/dev/urandom", O_RDONLY); if (fd >= 0) { read(fd, &seed, sizeof(unsigned int)); close(fd); } srand(seed); rand_seeded = 1; } if (max == 0) return SPL_NEW_FLOAT((double)rand() / (double)RAND_MAX); if (max < 0) return SPL_NEW_INT(rand()); return SPL_NEW_INT(rand() % max); } /** * Seeds the random number generater. When called with no argument a seed * is autogenerated from /dev/urandom. This is also done automatically when * rand() is called without calling srand() before. */ // builtin srand(seed) static struct spl_node *spl_builtin_srand(struct spl_task *task, void UNUSED(*data)) { unsigned int seed = spl_clib_get_int(task); if (!seed) { seed = (getpid() << 16) ^ time(0); int fd = open("/dev/urandom", O_RDONLY); if (fd >= 0) { read(fd, &seed, sizeof(unsigned int)); close(fd); } } srand(seed); rand_seeded = 1; return 0; } /** * Returns 1 if the specified builtin function is declared and 0 otherwise. */ // builtin declared_builtin(function_name) static struct spl_node *spl_builtin_declared_builtin(struct spl_task *task, void UNUSED(*data)) { char *fname = spl_clib_get_string(task); return SPL_NEW_INT(spl_clib_check(task->vm, fname)); } /** * Returns the section of given string * starting at offset with length number of characters. * * If length is 0 or out of range (offset + length > total length), * then the length is adjusted to include the entire string starting * at given offset. * * TODO: check if this causes memory leaks. * * TODO: this does not work with strings containing UTF-8 characters. */ // builting substring(string,offset,length) static struct spl_node *spl_builtin_substring(struct spl_task *task, void UNUSED(*data)) { char *text = spl_clib_get_string(task); int offset = spl_clib_get_int(task); int length = spl_clib_get_int(task); struct spl_string *original = spl_string_printf(0,0,0,"%s",text); //printf("substring settings: original text: %s; offset: %d; length: %d\n",original->text,offset,length); if (offset < 0 || length <= 0 || (unsigned int)offset > original->total_len) { //printf("invalid range settings, returning empty string\n"); return SPL_NEW_STRING_DUP(""); } else { if ((unsigned int)offset + (unsigned int)length > original->total_len) length = original->total_len - offset; struct spl_string *substr = spl_string_split(original,offset,length); //printf("spl substring: %s\n",substr->text); struct spl_node *result = spl_get(0); spl_set_spl_string(result,substr); //printf("result value: %s\n",result->value_string->text); return result; } } /** * The setlocale() function is used to set or query the program's current locale. * * This is a simple wrapper for the C setlocale() function. The 'category' * argument is passed as string. E.g.: * * setlocale("LC_ALL", "C"); */ // builtin setlocale(category, locale) static struct spl_node *spl_builtin_setlocale(struct spl_task UNUSED(*task), void UNUSED(*data)) { #ifdef ENABLE_GETTEXT_SUPPORT char *category = spl_clib_get_string(task); char *locale = spl_clib_get_string(task); char *retval = 0; if (!strcmp(category, "LC_ALL")) retval = setlocale(LC_ALL, locale); else if (!strcmp(category, "LC_COLLATE")) retval = setlocale(LC_COLLATE, locale); else if (!strcmp(category, "LC_CTYPE")) retval = setlocale(LC_CTYPE, locale); else if (!strcmp(category, "LC_MESSAGES")) retval = setlocale(LC_MESSAGES, locale); else if (!strcmp(category, "LC_MONETARY")) retval = setlocale(LC_MONETARY, locale); else if (!strcmp(category, "LC_NUMERIC")) retval = setlocale(LC_NUMERIC, locale); else if (!strcmp(category, "LC_TIME")) retval = setlocale(LC_TIME, locale); return retval ? SPL_NEW_STRING_DUP(retval) : 0; #else return 0; #endif } /** * The bindtextdomain() function sets the base directory of the hierarchy * containing message catalogs for a given message domain. * * This is a simple wrapper for the C bindtextdomain() function. */ // builtin bindtextdomain(domain, directory) static struct spl_node *spl_builtin_bindtextdomain(struct spl_task UNUSED(*task), void UNUSED(*data)) { #ifdef ENABLE_GETTEXT_SUPPORT char *domain = spl_clib_get_string(task); char *newdirectory = spl_clib_get_string(task); if (!newdirectory || !*newdirectory) newdirectory = 0; char *retval = bindtextdomain(domain, newdirectory); return retval ? SPL_NEW_STRING_DUP(retval) : 0; #else return 0; #endif } /** * The textdomain() function sets or retrieves the current message domain. * * This is a simple wrapper for the C textdomain() function. */ // builtin textdomain(domain) static struct spl_node *spl_builtin_textdomain(struct spl_task UNUSED(*task), void UNUSED(*data)) { #ifdef ENABLE_GETTEXT_SUPPORT char *newdomain = spl_clib_get_string(task); if (!newdomain || !*newdomain) newdomain = 0; char *retval = textdomain(newdomain); return retval ? SPL_NEW_STRING_DUP(retval) : 0; #else return 0; #endif } /** * The gettext() function attempts to translate a text string into the user's * native language, by looking up the translation in a message catalog. * * This is a simple wrapper for the C gettext() function. */ // builtin gettext(message) static struct spl_node *spl_builtin_gettext(struct spl_task *task, void UNUSED(*data)) { #ifdef ENABLE_GETTEXT_SUPPORT char *message = spl_clib_get_string(task); char *retval = gettext(message); return retval ? SPL_NEW_STRING_DUP(retval) : 0; #else char *message = spl_clib_get_string(task); return SPL_NEW_STRING_DUP(message); #endif } /** * The dgettext() function attempts to translate a text string into the user's * native language, by looking up the translation in a message catalog. * * This is a simple wrapper for the C gettext() function. */ // builtin dgettext(domain, message) static struct spl_node *spl_builtin_dgettext(struct spl_task *task, void UNUSED(*data)) { #ifdef ENABLE_GETTEXT_SUPPORT char *domain = spl_clib_get_string(task); char *message = spl_clib_get_string(task); char *retval = dgettext(domain && *domain ? domain : 0, message); return retval ? SPL_NEW_STRING_DUP(retval) : 0; #else spl_clib_get_string(task); char *message = spl_clib_get_string(task); return SPL_NEW_STRING_DUP(message); #endif } /** * This function is implementing the backend functionality of the SPL * translation prefix for strings (_). * * It translates the message using the [[gettext()]] function and then * substitutes '{N}' with the strings passed as additional arguments (N * beeing the index in @args) and '{}' with a simple '{'. */ // builtin _(message, domain, @args) static struct spl_node *spl_builtin_underscore(struct spl_task *task, void UNUSED(*data)) { char *message = spl_clib_get_string(task); #ifdef ENABLE_GETTEXT_SUPPORT char *domain = spl_clib_get_string(task); #else spl_clib_get_string(task); #endif int args_counter = spl_clib_get_argc(task); char *args[args_counter]; int args_len[args_counter]; for (int i=0; i '9') goto skip_format_error_A; arg = arg*10 + (translation[j] - '0'); } if (arg >= args_counter) goto skip_format_error_A; i = j; retval_len += args_len[arg]; continue; } skip_format_error_A: retval_len++; } char *retval = malloc(retval_len+1); char *retval_p = retval; for (int i=0; translation[i]; i++) { if (translation[i] == '{' && translation[i+1] == '}') { *(retval_p++) = '{'; i++; continue; } if (translation[i] == '{') { int j = i+1; int arg = 0; for (; translation[j] != '}'; j++) { if (translation[j] < '0' || translation[j] > '9') goto skip_format_error_B; arg = arg*10 + (translation[j] - '0'); } if (arg >= args_counter) goto skip_format_error_B; i = j; memcpy(retval_p, args[arg], args_len[arg]); retval_p += args_len[arg]; continue; } skip_format_error_B: *(retval_p++) = translation[i]; } *retval_p = 0; assert(retval_p == retval + retval_len); return SPL_NEW_STRING(retval); } #define MATH1(x) spl_clib_reg(vm, #x, spl_builtin_math1_wrapper, x) struct spl_node *spl_builtin_math1_wrapper(struct spl_task *task, void UNUSED(*data)) { double (*mlib_func)(double) = data; double arg1 = spl_clib_get_float(task); return SPL_NEW_FLOAT(mlib_func(arg1)); } #define MATH2(x) spl_clib_reg(vm, #x, spl_builtin_math2_wrapper, x) struct spl_node *spl_builtin_math2_wrapper(struct spl_task *task, void UNUSED(*data)) { double (*mlib_func)(double, double) = data; double arg1 = spl_clib_get_float(task); double arg2 = spl_clib_get_float(task); return SPL_NEW_FLOAT(mlib_func(arg1, arg2)); } // round() and rint() are C99 functions and not provided by C89 libs static double my_round(double x) { return x > 0 ? floor (x + 0.5) : ceil (x - 0.5); } void spl_builtin_register_all(struct spl_vm *vm) { spl_clib_reg(vm, "write", spl_builtin_write, 0); spl_clib_reg(vm, "read", spl_builtin_read, 0); spl_clib_reg(vm, "chr", spl_builtin_chr, 0); spl_clib_reg(vm, "ord", spl_builtin_ord, 0); spl_clib_reg(vm, "hex", spl_builtin_hex, 0); spl_clib_reg(vm, "oct", spl_builtin_oct, 0); spl_clib_reg(vm, "bin", spl_builtin_bin, 0); spl_clib_reg(vm, "fmt", spl_builtin_fmt, 0); spl_clib_reg(vm, "rand", spl_builtin_rand, 0); spl_clib_reg(vm, "srand", spl_builtin_srand, 0); spl_clib_reg(vm, "declared_builtin", spl_builtin_declared_builtin, 0); spl_clib_reg(vm, "substring", spl_builtin_substring, 0); spl_clib_reg(vm, "setlocale", spl_builtin_setlocale, 0); spl_clib_reg(vm, "bindtextdomain", spl_builtin_bindtextdomain, 0); spl_clib_reg(vm, "textdomain", spl_builtin_textdomain, 0); spl_clib_reg(vm, "gettext", spl_builtin_gettext, 0); spl_clib_reg(vm, "dgettext", spl_builtin_dgettext, 0); spl_clib_reg(vm, "_", spl_builtin_underscore, 0); /** * arc cosine function */ // builtin acos(x) MATH1(acos); /** * arc sine function */ // builtin asin) MATH1(asin); /** * arc tangent function */ // builtin atan(x) MATH1(atan); /** * arc tangent function of two variables */ // builtin atan2(x, y) MATH2(atan2); /** * cosine function */ // builtin cos(x) MATH1(cos); /** * sine function */ // builtin sin(x) MATH1(sin); /** * tangent function */ // builtin tan(x) MATH1(tan); /** * square root function */ // builtin sqrt(x) MATH1(sqrt); /** * round to nearest integer */ // builtin round(x) spl_clib_reg(vm, "round", spl_builtin_math1_wrapper, my_round); /** * smallest integral value not less than argument */ // builtin ceil(x) MATH1(ceil); /** * largest integral value not greater than argument */ // builtin floor(x) MATH1(floor); }