/**
* \file funstr.c
*
* \brief String functions for mushcode.
*
*
*/
#include "copyrite.h"
#include "config.h"
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <locale.h>
#include "conf.h"
#include "ansi.h"
#include "externs.h"
#include "case.h"
#include "match.h"
#include "parse.h"
#include "pueblo.h"
#include "attrib.h"
#include "flags.h"
#include "dbdefs.h"
#include "mushdb.h"
#include "htab.h"
#include "lock.h"
#include "confmagic.h"
#ifdef WIN32
#define LC_MESSAGES 6
#pragma warning( disable : 4761) /* NJG: disable warning re conversion */
#endif
#ifdef __APPLE__
#define LC_MESSAGES 6
#endif
HASHTAB htab_tag; /**< Hash table of safe html tags */
#define MAX_COLS 32 /**< Maximum number of columns for align() */
static int wraplen(char *str, int maxlen);
static int align_one_line(char *buff, char **bp, int ncols,
int cols[MAX_COLS], int calign[MAX_COLS],
char *ptrs[MAX_COLS], ansi_string *as[MAX_COLS],
int linenum, char *fieldsep, int fslen, char *linesep,
int lslen, char filler);
static int comp_gencomp(dbref executor, char *left, char *right, char *type);
void init_tag_hashtab(void);
void init_pronouns(void);
/** Return an indicator of a player's gender.
* \param player player whose gender is to be checked.
* \retval 0 neuter.
* \retval 1 female.
* \retval 2 male.
* \retval 3 plural.
*/
int
get_gender(dbref player)
{
ATTR *a;
a = atr_get(player, "SEX");
if (!a)
return 0;
switch (*atr_value(a)) {
case 'T':
case 't':
case 'P':
case 'p':
return 3;
case 'M':
case 'm':
return 2;
case 'F':
case 'f':
case 'W':
case 'w':
return 1;
default:
return 0;
}
}
char *subj[4]; /**< Subjective pronouns */
char *poss[4]; /**< Possessive pronouns */
char *obj[4]; /**< Objective pronouns */
char *absp[4]; /**< Absolute possessive pronouns */
/** Macro to set a pronoun entry based on whether we're translating or not */
#define SET_PRONOUN(p,v,u) p = strdup((translate) ? (v) : (u))
/** Initialize the pronoun translation strings.
* This function sets up the values of the arrays of subjective,
* possessive, objective, and absolute possessive pronouns with
* locale-appropriate values.
*/
void
init_pronouns(void)
{
int translate = 0;
#ifdef HAS_SETLOCALE
char *loc;
if ((loc = setlocale(LC_MESSAGES, NULL))) {
if (strcmp(loc, "C") && strncmp(loc, "en", 2))
translate = 1;
}
#endif
SET_PRONOUN(subj[0], T("pronoun:neuter,subjective"), "it");
SET_PRONOUN(subj[1], T("pronoun:feminine,subjective"), "she");
SET_PRONOUN(subj[2], T("pronoun:masculine,subjective"), "he");
SET_PRONOUN(subj[3], T("pronoun:plural,subjective"), "they");
SET_PRONOUN(poss[0], T("pronoun:neuter,possessive"), "its");
SET_PRONOUN(poss[1], T("pronoun:feminine,possessive"), "her");
SET_PRONOUN(poss[2], T("pronoun:masculine,possessive"), "his");
SET_PRONOUN(poss[3], T("pronoun:plural,possessive"), "their");
SET_PRONOUN(obj[0], T("pronoun:neuter,objective"), "it");
SET_PRONOUN(obj[1], T("pronoun:feminine,objective"), "her");
SET_PRONOUN(obj[2], T("pronoun:masculine,objective"), "him");
SET_PRONOUN(obj[3], T("pronoun:plural,objective"), "them");
SET_PRONOUN(absp[0], T("pronoun:neuter,absolute possessive"), "its");
SET_PRONOUN(absp[1], T("pronoun:feminine,absolute possessive"), "hers");
SET_PRONOUN(absp[2], T("pronoun:masculine,absolute possessive"), "his");
SET_PRONOUN(absp[3], T("pronoun:plural,absolute possessive "), "theirs");
}
#undef SET_PRONOUN
/* ARGSUSED */
FUNCTION(fun_isword)
{
/* is every character a letter? */
char *p;
if (!args[0] || !*args[0]) {
safe_chr('0', buff, bp);
return;
}
for (p = args[0]; *p; p++) {
if (!isalpha((unsigned char) *p)) {
safe_chr('0', buff, bp);
return;
}
}
safe_chr('1', buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_capstr)
{
char *p;
p = skip_leading_ansi(args[0]);
if (!p) {
safe_strl(args[0], arglens[0], buff, bp);
return;
} else if (p != args[0]) {
char x = *p;
*p = '\0';
safe_strl(args[0], p - args[0], buff, bp);
*p = x;
}
if (*p) {
safe_chr(UPCASE(*p), buff, bp);
p++;
}
if (*p)
safe_str(p, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_art)
{
/* checks a word and returns the appropriate article, "a" or "an" */
char c;
char *p = skip_leading_ansi(args[0]);
if (!p) {
safe_chr('a', buff, bp);
return;
}
c = tolower(*p);
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
safe_str("an", buff, bp);
else
safe_chr('a', buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_subj)
{
dbref thing;
thing = match_thing(executor, args[0]);
if (thing == NOTHING) {
safe_str(T(e_match), buff, bp);
return;
}
safe_str(subj[get_gender(thing)], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_poss)
{
dbref thing;
thing = match_thing(executor, args[0]);
if (thing == NOTHING) {
safe_str(T(e_match), buff, bp);
return;
}
safe_str(poss[get_gender(thing)], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_obj)
{
dbref thing;
thing = match_thing(executor, args[0]);
if (thing == NOTHING) {
safe_str(T(e_match), buff, bp);
return;
}
safe_str(obj[get_gender(thing)], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_aposs)
{
dbref thing;
thing = match_thing(executor, args[0]);
if (thing == NOTHING) {
safe_str(T(e_match), buff, bp);
return;
}
safe_str(absp[get_gender(thing)], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_alphamax)
{
char amax[BUFFER_LEN];
char *c;
int j, m = 0;
size_t len;
c = remove_markup(args[0], &len);
memcpy(amax, c, len);
for (j = 1; j < nargs; j++) {
c = remove_markup(args[j], &len);
if (strcoll(amax, c) < 0) {
memcpy(amax, c, len);
m = j;
}
}
safe_strl(args[m], arglens[m], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_alphamin)
{
char amin[BUFFER_LEN];
char *c;
int j, m = 0;
size_t len;
c = remove_markup(args[0], &len);
memcpy(amin, c, len);
for (j = 1; j < nargs; j++) {
c = remove_markup(args[j], &len);
if (strcoll(amin, c) > 0) {
memcpy(amin, c, len);
m = j;
}
}
safe_strl(args[m], arglens[m], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_strlen)
{
safe_integer(ansi_strlen(args[0]), buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_mid)
{
ansi_string *as;
int pos, len;
if (!is_integer(args[1]) || !is_integer(args[2])) {
safe_str(T(e_ints), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
pos = parse_integer(args[1]);
len = parse_integer(args[2]);
if ((pos < 0) || (len < 0)) {
safe_str(T(e_range), buff, bp);
free_ansi_string(as);
return;
}
safe_ansi_string(as, pos, len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_left)
{
int len;
ansi_string *as;
if (!is_integer(args[1])) {
safe_str(T(e_int), buff, bp);
return;
}
len = parse_integer(args[1]);
if (len < 0) {
safe_str(T(e_range), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
safe_ansi_string(as, 0, len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_right)
{
int len;
ansi_string *as;
if (!is_integer(args[1])) {
safe_str(T(e_int), buff, bp);
return;
}
len = parse_integer(args[1]);
if (len < 0) {
safe_str(T(e_range), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
if ((size_t) len > as->len)
safe_strl(args[0], arglens[0], buff, bp);
else
safe_ansi_string(as, as->len - len, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_strinsert)
{
/* Insert a string into another */
ansi_string *as;
int pos;
if (!is_integer(args[1])) {
safe_str(e_int, buff, bp);
return;
}
pos = parse_integer(args[1]);
if (pos < 0) {
safe_str(T(e_range), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
if ((size_t) pos > as->len) {
/* Fast special case - concatenate args[2] to args[0] */
safe_strl(args[0], arglens[0], buff, bp);
safe_strl(args[2], arglens[0], buff, bp);
free_ansi_string(as);
return;
}
safe_ansi_string(as, 0, pos, buff, bp);
safe_strl(args[2], arglens[2], buff, bp);
safe_ansi_string(as, pos, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_delete)
{
ansi_string *as;
int pos, num;
if (!is_integer(args[1]) || !is_integer(args[2])) {
safe_str(T(e_ints), buff, bp);
return;
}
pos = parse_integer(args[1]);
num = parse_integer(args[2]);
if (pos < 0) {
safe_str(T(e_range), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
if ((size_t) pos > as->len || num <= 0) {
safe_strl(args[0], arglens[0], buff, bp);
free_ansi_string(as);
return;
}
safe_ansi_string(as, 0, pos, buff, bp);
safe_ansi_string(as, pos + num, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_strreplace)
{
ansi_string *as, *anew;
int start, len, end;
if (!is_integer(args[1]) || !is_integer(args[2])) {
safe_str(T(e_ints), buff, bp);
return;
}
start = parse_integer(args[1]);
len = parse_integer(args[2]);
if (start < 0 || len < 0) {
safe_str(T(e_range), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
anew = parse_ansi_string(args[3]);
safe_ansi_string(as, 0, start, buff, bp);
safe_ansi_string(anew, 0, anew->len, buff, bp);
end = start + len;
if ((size_t) end < as->len)
safe_ansi_string(as, end, as->len - end, buff, bp);
free_ansi_string(as);
free_ansi_string(anew);
}
static int
comp_gencomp(dbref executor, char *left, char *right, char *type)
{
int c;
c = gencomp(executor, left, right, type);
return (c > 0 ? 1 : (c < 0 ? -1 : 0));
}
/* ARGSUSED */
FUNCTION(fun_comp)
{
char type = 'A';
if (nargs == 3 && !(args[2] && *args[2])) {
safe_str(T("#-1 INVALID THIRD ARGUMENT"), buff, bp);
return;
} else if (nargs == 3) {
type = toupper(*args[2]);
}
switch (type) {
case 'A': /* Case-sensitive lexicographic */
{
char left[BUFFER_LEN], right[BUFFER_LEN], *l, *r;
size_t llen, rlen;
l = remove_markup(args[0], &llen);
memcpy(left, l, llen);
r = remove_markup(args[1], &rlen);
memcpy(right, r, rlen);
safe_integer(comp_gencomp(executor, left, right, ALPHANUM_LIST), buff,
bp);
return;
}
case 'I': /* Case-insensitive lexicographic */
{
char left[BUFFER_LEN], right[BUFFER_LEN], *l, *r;
size_t llen, rlen;
l = remove_markup(args[0], &llen);
memcpy(left, l, llen);
r = remove_markup(args[1], &rlen);
memcpy(right, r, rlen);
safe_integer(comp_gencomp(executor, left, right, INSENS_ALPHANUM_LIST),
buff, bp);
return;
}
case 'N': /* Integers */
if (!is_strict_integer(args[0]) || !is_strict_integer(args[1])) {
safe_str(T(e_ints), buff, bp);
return;
}
safe_integer(comp_gencomp(executor, args[0], args[1], NUMERIC_LIST), buff,
bp);
return;
case 'F':
if (!is_strict_number(args[0]) || !is_strict_number(args[1])) {
safe_str(T(e_nums), buff, bp);
return;
}
safe_integer(comp_gencomp(executor, args[0], args[1], FLOAT_LIST), buff,
bp);
return;
case 'D':
{
dbref a, b;
a = parse_objid(args[0]);
b = parse_objid(args[1]);
if (a == NOTHING || b == NOTHING) {
safe_str(T("#-1 INVALID DBREF"), buff, bp);
return;
}
safe_integer(comp_gencomp(executor, args[0], args[1], DBREF_LIST), buff,
bp);
return;
}
default:
safe_str(T("#-1 INVALID THIRD ARGUMENT"), buff, bp);
return;
}
}
/* ARGSUSED */
FUNCTION(fun_pos)
{
char tbuf[BUFFER_LEN];
char *pos;
size_t len;
pos = remove_markup(args[1], &len);
memcpy(tbuf, pos, len);
pos = strstr(tbuf, remove_markup(args[0], NULL));
if (pos)
safe_integer(pos - tbuf + 1, buff, bp);
else
safe_str("#-1", buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_lpos)
{
char *pos;
char c = ' ';
size_t n, len;
int first = 1;
if (args[1][0])
c = args[1][0];
pos = remove_markup(args[0], &len);
for (n = 0; n < len; n++)
if (pos[n] == c) {
if (first)
first = 0;
else
safe_chr(' ', buff, bp);
safe_integer(n, buff, bp);
}
}
/* ARGSUSED */
FUNCTION(fun_strmatch)
{
char tbuf[BUFFER_LEN];
char *t;
size_t len;
/* matches a wildcard pattern for an _entire_ string */
t = remove_markup(args[0], &len);
memcpy(tbuf, t, len);
safe_boolean(quick_wild(remove_markup(args[1], NULL), tbuf), buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_strcat)
{
int j;
for (j = 0; j < nargs; j++)
safe_strl(args[j], arglens[j], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_flip)
{
ansi_string *as;
int p, n;
as = parse_ansi_string(args[0]);
populate_codes(as);
for (p = 0, n = as->len - 1; p < n; p++, n--) {
char *tcode;
char t;
tcode = as->codes[p];
t = as->text[p];
as->codes[p] = as->codes[n];
as->text[p] = as->text[n];
as->codes[n] = tcode;
as->text[n] = t;
}
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_merge)
{
/* given s1, s2, and a list of characters, for each character in s1,
* if the char is in the list, replace it with the corresponding
* char in s2.
*/
char *str, *rep;
char matched[UCHAR_MAX + 1];
/* do length checks first */
if (arglens[0] != arglens[1]) {
safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
return;
}
memset(matched, 0, sizeof matched);
/* find the characters to look for */
if (!args[2] || !*args[2])
matched[(unsigned char) ' '] = 1;
else {
unsigned char *p;
for (p = (unsigned char *) args[2]; p && *p; p++)
matched[*p] = 1;
}
/* walk strings, copy from the appropriate string */
for (str = args[0], rep = args[1]; *str && *rep; str++, rep++) {
*str = matched[(unsigned char) *str] ? *rep : *str;
}
safe_str(args[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_tr)
{
/* given str, s1, s2, for each character in str, if the char
* is in s1, replace it with the char at the same index in s2.
*/
char charmap[256];
char instr[BUFFER_LEN], outstr[BUFFER_LEN];
char rawstr[BUFFER_LEN];
char *ip, *op;
size_t i, len;
char *c;
ansi_string *as;
/* No ansi allowed in find or replace lists */
c = remove_markup(args[1], &len);
memcpy(rawstr, c, len);
/* do length checks first */
for (i = 0; i < 256; i++) {
charmap[i] = (char) i;
}
ip = instr;
op = outstr;
for (i = 0; i < len; i++) {
safe_chr(rawstr[i], instr, &ip);
/* Handle a range of characters */
if (i != len - 1 && rawstr[i + 1] == '-' && i != len - 2) {
int dir, sentinel, cur;
if (rawstr[i] < rawstr[i + 2])
dir = 1;
else
dir = -1;
sentinel = rawstr[i + 2] + dir;
cur = rawstr[i] + dir;
while (cur != sentinel) {
safe_chr((char) cur, instr, &ip);
cur += dir;
}
i += 2;
}
}
c = remove_markup(args[2], &len);
memcpy(rawstr, c, len);
for (i = 0; i < len; i++) {
safe_chr(rawstr[i], outstr, &op);
/* Handle a range of characters */
if (i != len - 1 && rawstr[i + 1] == '-' && i != len - 2) {
int dir, sentinel, cur;
if (rawstr[i] < rawstr[i + 2])
dir = 1;
else
dir = -1;
sentinel = rawstr[i + 2] + dir;
cur = rawstr[i] + dir;
while (cur != sentinel) {
safe_chr((char) cur, outstr, &op);
cur += dir;
}
i += 2;
}
}
if ((ip - instr) != (op - outstr)) {
safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
return;
}
len = ip - instr;
for (i = 0; i < len; i++)
charmap[(unsigned char) instr[i]] = outstr[i];
/* walk the string, translating characters */
as = parse_ansi_string(args[0]);
populate_codes(as);
len = as->len;
for (i = 0; i < len; i++) {
as->text[i] = charmap[(unsigned char) as->text[i]];
}
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_lcstr)
{
char *p, *y;
p = args[0];
while (*p) {
y = skip_leading_ansi(p);
if (y != p) {
char t;
t = *y;
*y = '\0';
safe_str(p, buff, bp);
*y = t;
p = y;
}
if (*p) {
safe_chr(DOWNCASE(*p), buff, bp);
p++;
}
}
}
/* ARGSUSED */
FUNCTION(fun_ucstr)
{
char *p, *y;
p = args[0];
while (*p) {
y = skip_leading_ansi(p);
if (y != p) {
char t;
t = *y;
*y = '\0';
safe_str(p, buff, bp);
*y = t;
p = y;
}
if (*p) {
safe_chr(UPCASE(*p), buff, bp);
p++;
}
}
}
/* ARGSUSED */
FUNCTION(fun_repeat)
{
int times;
char *ap;
if (!is_integer(args[1])) {
safe_str(T(e_int), buff, bp);
return;
}
times = parse_integer(args[1]);
if (times < 0) {
safe_str(T("#-1 ARGUMENT MUST BE NON-NEGATIVE INTEGER"), buff, bp);
return;
}
if (!*args[0])
return;
/* Special-case repeating one character */
if (arglens[0] == 1) {
safe_fill(args[0][0], times, buff, bp);
return;
}
/* Do the repeat in O(lg n) time. */
/* This takes advantage of the fact that we're given a BUFFER_LEN
* buffer for args[0] that we are free to trash. Huzzah! */
ap = args[0] + arglens[0];
while (times) {
if (times & 1) {
if (safe_strl(args[0], arglens[0], buff, bp) != 0)
break;
}
safe_str(args[0], args[0], &ap);
*ap = '\0';
arglens[0] = strlen(args[0]);
times = times >> 1;
}
}
/* ARGSUSED */
FUNCTION(fun_scramble)
{
int n, i, j;
ansi_string *as;
if (!*args[0])
return;
as = parse_ansi_string(args[0]);
populate_codes(as);
n = as->len;
for (i = 0; i < n; i++) {
char t, *tcode;
j = get_random_long(i, n - 1);
t = as->text[j];
as->text[j] = as->text[i];
as->text[i] = t;
tcode = as->codes[j];
as->codes[j] = as->codes[i];
as->codes[i] = tcode;
}
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_ljust)
{
/* pads a string with trailing blanks (or other fill character) */
size_t spaces, len;
char sep;
if (!is_uinteger(args[1])) {
safe_str(T(e_uint), buff, bp);
return;
}
len = ansi_strlen(args[0]);
spaces = parse_uinteger(args[1]);
if (spaces >= BUFFER_LEN)
spaces = BUFFER_LEN - 1;
if (len >= spaces) {
safe_strl(args[0], arglens[0], buff, bp);
return;
}
spaces -= len;
if (!delim_check(buff, bp, nargs, args, 3, &sep))
return;
safe_strl(args[0], arglens[0], buff, bp);
safe_fill(sep, spaces, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_rjust)
{
/* pads a string with leading blanks (or other fill character) */
size_t spaces, len;
char sep;
if (!is_uinteger(args[1])) {
safe_str(T(e_uint), buff, bp);
return;
}
len = ansi_strlen(args[0]);
spaces = parse_uinteger(args[1]);
if (spaces >= BUFFER_LEN)
spaces = BUFFER_LEN - 1;
if (len >= spaces) {
safe_strl(args[0], arglens[0], buff, bp);
return;
}
spaces -= len;
if (!delim_check(buff, bp, nargs, args, 3, &sep))
return;
safe_fill(sep, spaces, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_center)
{
/* pads a string with leading blanks (or other fill character) */
size_t width, len, lsp, rsp;
char sep;
if (!is_uinteger(args[1])) {
safe_str(T(e_uint), buff, bp);
return;
}
width = parse_uinteger(args[1]);
len = ansi_strlen(args[0]);
if (len >= width) {
safe_strl(args[0], arglens[0], buff, bp);
return;
}
rsp = width - len;
lsp = rsp / 2;
rsp -= lsp;
if (lsp >= BUFFER_LEN)
lsp = BUFFER_LEN - 1;
if (rsp >= BUFFER_LEN)
rsp = BUFFER_LEN - 1;
if (!delim_check(buff, bp, nargs, args, 3, &sep))
return;
safe_fill(sep, lsp, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
safe_fill(sep, rsp, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_foreach)
{
/* Like map(), but it operates on a string, rather than on a list,
* calling a user-defined function for each character in the string.
* No delimiter is inserted between the results.
*/
dbref thing;
ATTR *attrib;
char const *ap, *lp;
char *asave, cbuf[2];
char *tptr[2];
char place[SBUF_LEN];
int placenr = 0;
int funccount;
char *oldbp;
char start, end;
char letters[BUFFER_LEN];
size_t len;
if (nargs >= 3) {
if (!delim_check(buff, bp, nargs, args, 3, &start))
return;
}
if (nargs == 4) {
if (!delim_check(buff, bp, nargs, args, 4, &end))
return;
} else {
end = '\0';
}
/* find our object and attribute */
parse_anon_attrib(executor, args[0], &thing, &attrib);
if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
free_anon_attrib(attrib);
return;
}
strcpy(place, "0");
asave = safe_atr_value(attrib);
/* save our stack */
tptr[0] = global_eval_context.wenv[0];
tptr[1] = global_eval_context.wenv[1];
global_eval_context.wenv[1] = place;
ap = remove_markup(args[1], &len);
memcpy(letters, ap, len);
lp = trim_space_sep(letters, ' ');
if (nargs >= 3) {
char *tmp = strchr(lp, start);
if (!tmp) {
safe_str(lp, buff, bp);
free((Malloc_t) asave);
free_anon_attrib(attrib);
global_eval_context.wenv[1] = tptr[1];
return;
}
oldbp = place;
placenr = (tmp + 1) - lp;
safe_integer_sbuf(placenr, place, &oldbp);
oldbp = *bp;
*tmp = '\0';
safe_str(lp, buff, bp);
lp = tmp + 1;
}
cbuf[1] = '\0';
global_eval_context.wenv[0] = cbuf;
oldbp = *bp;
funccount = pe_info->fun_invocations;
while (*lp && *lp != end) {
*cbuf = *lp++;
ap = asave;
if (process_expression(buff, bp, &ap, thing, executor, enactor,
PE_DEFAULT, PT_DEFAULT, pe_info))
break;
if (*bp == oldbp && pe_info->fun_invocations == funccount)
break;
oldbp = place;
safe_integer_sbuf(++placenr, place, &oldbp);
*oldbp = '\0';
oldbp = *bp;
funccount = pe_info->fun_invocations;
}
if (*lp)
safe_str(lp + 1, buff, bp);
free((Malloc_t) asave);
free_anon_attrib(attrib);
global_eval_context.wenv[0] = tptr[0];
global_eval_context.wenv[1] = tptr[1];
}
extern char escaped_chars[UCHAR_MAX + 1];
/* ARGSUSED */
FUNCTION(fun_secure)
{
/* this function smashes all occurences of "unsafe" characters in a string.
* "unsafe" characters are defined by the escaped_chars table.
* these characters get replaced by spaces
*/
unsigned char *p;
for (p = (unsigned char *) args[0]; *p; p++)
if (escaped_chars[*p])
*p = ' ';
safe_strl(args[0], arglens[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_escape)
{
unsigned char *s;
if (arglens[0]) {
safe_chr('\\', buff, bp);
for (s = (unsigned char *) args[0]; *s; s++) {
if ((s != (unsigned char *) args[0]) && escaped_chars[*s])
safe_chr('\\', buff, bp);
safe_chr(*s, buff, bp);
}
}
}
/* ARGSUSED */
FUNCTION(fun_trim)
{
/* Similar to squish() but it doesn't trim spaces in the center, and
* takes a delimiter argument and trim style.
*/
char *p, *q, sep;
int trim;
int trim_style_arg, trim_char_arg;
/* Alas, PennMUSH and TinyMUSH used different orders for the arguments.
* We'll give the users an option about it
*/
if (!strcmp(called_as, "TRIMTINY")) {
trim_style_arg = 2;
trim_char_arg = 3;
} else if (!strcmp(called_as, "TRIMPENN")) {
trim_style_arg = 3;
trim_char_arg = 2;
} else if (TINY_TRIM_FUN) {
trim_style_arg = 2;
trim_char_arg = 3;
} else {
trim_style_arg = 3;
trim_char_arg = 2;
}
if (!delim_check(buff, bp, nargs, args, trim_char_arg, &sep))
return;
/* If a trim style is provided, it must be the third argument. */
if (nargs >= trim_style_arg) {
switch (DOWNCASE(*args[trim_style_arg - 1])) {
case 'l':
trim = 1;
break;
case 'r':
trim = 2;
break;
default:
trim = 3;
break;
}
} else
trim = 3;
/* We will never need to check for buffer length overrunning, since
* we will always get a smaller string. Thus, we can copy at the
* same time we skip stuff.
*/
/* If necessary, skip over the leading stuff. */
p = args[0];
if (trim != 2) {
while (*p == sep)
p++;
}
/* Cut off the trailing stuff, if appropriate. */
if ((trim != 1) && (*p != '\0')) {
q = args[0] + arglens[0] - 1;
while ((q > p) && (*q == sep))
q--;
q[1] = '\0';
}
safe_str(p, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_lit)
{
/* Just returns the argument, literally */
safe_strl(args[0], arglens[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_squish)
{
/* zaps leading and trailing spaces, and reduces other spaces to a single
* space. This only applies to the literal space character, and not to
* tabs, newlines, etc.
* We do not need to check for buffer length overflows, since we're
* never going to end up with a longer string.
*/
char *tp;
char sep;
/* Figure out the character to squish */
if (!delim_check(buff, bp, nargs, args, 2, &sep))
return;
/* get rid of trailing spaces first, so we don't have to worry about
* them later.
*/
tp = args[0] + arglens[0] - 1;
while ((tp > args[0]) && (*tp == sep))
tp--;
tp[1] = '\0';
for (tp = args[0]; *tp == sep; tp++) /* skip leading spaces */
;
while (*tp) {
safe_chr(*tp, buff, bp);
if (*tp == sep)
while (*tp == sep)
tp++;
else
tp++;
}
}
/* ARGSUSED */
FUNCTION(fun_space)
{
int s;
if (!is_uinteger(args[0])) {
safe_str(T(e_uint), buff, bp);
return;
}
s = parse_integer(args[0]);
safe_fill(' ', s, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_beep)
{
int k;
/* this function prints 1 to 5 beeps. The alert character '\a' is
* an ANSI C invention; non-ANSI-compliant implementations may ignore
* the '\' character and just print an 'a', or do something else nasty,
* so we define it to be something reasonable in ansi.h.
*/
if (nargs) {
if (!is_integer(args[0])) {
safe_str(T(e_int), buff, bp);
return;
}
k = parse_integer(args[0]);
} else
k = 1;
if (!Hasprivs(executor) || (k <= 0) || (k > 5)) {
safe_str(T(e_perm), buff, bp);
return;
}
safe_fill(BEEP_CHAR, k, buff, bp);
}
/** Initialize the html tag hash table with all the safe tags from HTML 4.0 */
void
init_tag_hashtab(void)
{
static char dummy = 1;
int i = 0;
static const char *safetags[] = { "A", "B", "I", "U", "STRONG", "EM",
"ADDRESS", "BLOCKQUOTE", "CENTER", "DEL", "DIV",
"H1", "H2", "H3", "H4", "H5", "H6", "HR", "INS",
"P", "PRE", "DIR", "DL", "DT", "DD", "LI", "MENU", "OL", "UL",
"TABLE", "CAPTION", "COLGROUP", "COL", "THEAD", "TFOOT",
"TBODY", "TR", "TD", "TH",
"BR", "FONT", "IMG", "SPAN", "SUB", "SUP",
"ABBR", "ACRONYM", "CITE", "CODE", "DFN", "KBD", "SAMP", "VAR",
"BIG", "S", "SMALL", "STRIKE", "TT",
NULL
};
hashinit(&htab_tag, 64, 1);
while (safetags[i]) {
hashadd(safetags[i], (void *) &dummy, &htab_tag);
i++;
}
}
FUNCTION(fun_ord)
{
char *m;
size_t len = 0;
if (!args[0] || !args[0][0]) {
safe_str(T("#-1 FUNCTION EXPECTS ONE CHARACTER"), buff, bp);
return;
}
m = remove_markup(args[0], &len);
if (len != 2) /* len includes trailing nul */
safe_str(T("#-1 FUNCTION EXPECTS ONE CHARACTER"), buff, bp);
else if (isprint((unsigned char) *m))
safe_integer((unsigned char) *m, buff, bp);
else
safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);
}
FUNCTION(fun_chr)
{
int c;
if (!is_integer(args[0])) {
safe_str(T(e_uint), buff, bp);
return;
}
c = parse_integer(args[0]);
if (c < 0 || c > UCHAR_MAX)
safe_str(T("#-1 THIS ISN'T UNICODE"), buff, bp);
else if (isprint(c))
safe_chr(c, buff, bp);
else
safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);
}
FUNCTION(fun_accent)
{
if (arglens[0] != arglens[1]) {
safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
return;
}
safe_accent(args[0], args[1], arglens[0], buff, bp);
}
FUNCTION(fun_stripaccents)
{
int n;
for (n = 0; n < arglens[0]; n++) {
if (accent_table[(unsigned char) args[0][n]].base)
safe_str(accent_table[(unsigned char) args[0][n]].base, buff, bp);
else
safe_chr(args[0][n], buff, bp);
}
}
/* ARGSUSED */
FUNCTION(fun_html)
{
if (!Wizard(executor))
safe_str(T(e_perm), buff, bp);
else
safe_tag(args[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_tag)
{
int i;
if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
safe_str("#-1", buff, bp);
else {
safe_chr(TAG_START, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
for (i = 1; i < nargs; i++) {
if (ok_tag_attribute(executor, args[i])) {
safe_chr(' ', buff, bp);
safe_strl(args[i], arglens[i], buff, bp);
}
}
safe_chr(TAG_END, buff, bp);
}
}
/* ARGSUSED */
FUNCTION(fun_endtag)
{
if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
safe_str("#-1", buff, bp);
else
safe_tag_cancel(args[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_tagwrap)
{
if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0])))
safe_str("#-1", buff, bp);
else {
if (nargs == 2)
safe_tag_wrap(args[0], NULL, args[1], buff, bp, executor);
else
safe_tag_wrap(args[0], args[1], args[2], buff, bp, executor);
}
}
#define COL_FLASH (1) /**< ANSI flash attribute bit */
#define COL_HILITE (2) /**< ANSI hilite attribute bit */
#define COL_INVERT (4) /**< ANSI inverse attribute bit */
#define COL_UNDERSCORE (8) /**< ANSI underscore attribute bit */
#define VAL_FLASH (5) /**< ANSI flag attribute value */
#define VAL_HILITE (1) /**< ANSI hilite attribute value */
#define VAL_INVERT (7) /**< ANSI inverse attribute value */
#define VAL_UNDERSCORE (4) /**< ANSI underscore attribute value */
#define COL_BLACK (30) /**< ANSI color black */
#define COL_RED (31) /**< ANSI color red */
#define COL_GREEN (32) /**< ANSI color green */
#define COL_YELLOW (33) /**< ANSI color yellow */
#define COL_BLUE (34) /**< ANSI color blue */
#define COL_MAGENTA (35) /**< ANSI color magenta */
#define COL_CYAN (36) /**< ANSI color cyan */
#define COL_WHITE (37) /**< ANSI color white */
/** The ansi attributes associated with a character. */
typedef struct {
char flags; /**< Ansi text attributes */
char fore; /**< Ansi foreground color */
char back; /**< Ansi background color */
} ansi_data;
static void dump_ansi_codes(ansi_data * ad, char *buff, char **bp);
/** If we're adding y to x, do we need to add z as well? */
#define EDGE_UP(x,y,z) (((y) & (z)) && !((x) & (z)))
static void
dump_ansi_codes(ansi_data * ad, char *buff, char **bp)
{
static ansi_data old_ad = { 0, 0, 0 };
int f = 0;
if ((old_ad.fore && !ad->fore)
|| (old_ad.back && !ad->back)
|| ((old_ad.flags & ad->flags) != old_ad.flags)) {
safe_str(ANSI_NORMAL, buff, bp);
old_ad.flags = 0;
old_ad.fore = 0;
old_ad.back = 0;
}
if ((old_ad.fore == ad->fore)
&& (old_ad.back == ad->back)
&& (old_ad.flags == ad->flags))
/* If nothing has changed, don't bother doing anything.
* This stops the entirely pointless \e[m being generated. */
return;
safe_str(ANSI_BEGIN, buff, bp);
if (EDGE_UP(old_ad.flags, ad->flags, COL_FLASH)) {
if (f++)
safe_chr(';', buff, bp);
safe_integer(VAL_FLASH, buff, bp);
}
if (EDGE_UP(old_ad.flags, ad->flags, COL_HILITE)) {
if (f++)
safe_chr(';', buff, bp);
safe_integer(VAL_HILITE, buff, bp);
}
if (EDGE_UP(old_ad.flags, ad->flags, COL_INVERT)) {
if (f++)
safe_chr(';', buff, bp);
safe_integer(VAL_INVERT, buff, bp);
}
if (EDGE_UP(old_ad.flags, ad->flags, COL_UNDERSCORE)) {
if (f++)
safe_chr(';', buff, bp);
safe_integer(VAL_UNDERSCORE, buff, bp);
}
if (ad->fore != old_ad.fore) {
if (f++)
safe_chr(';', buff, bp);
safe_integer(ad->fore, buff, bp);
}
if (ad->back != old_ad.back) {
if (f++)
safe_chr(';', buff, bp);
safe_integer(ad->back + 10, buff, bp);
}
safe_str(ANSI_END, buff, bp);
old_ad = *ad;
}
/* ARGSUSED */
FUNCTION(fun_ansi)
{
static char tbuff[BUFFER_LEN];
static ansi_data stack[1024] = { {0, 0, 0} }, *sp = stack;
char const *arg0, *arg1;
char *tbp;
tbp = tbuff;
arg0 = args[0];
process_expression(tbuff, &tbp, &arg0, executor, caller, enactor,
PE_DEFAULT, PT_DEFAULT, pe_info);
*tbp = '\0';
sp[1] = sp[0];
sp++;
for (tbp = tbuff; *tbp; tbp++) {
switch (*tbp) {
case 'n': /* normal */
sp->flags = 0;
sp->fore = 0;
sp->back = 0;
break;
case 'f': /* flash */
sp->flags |= COL_FLASH;
break;
case 'h': /* hilite */
sp->flags |= COL_HILITE;
break;
case 'i': /* inverse */
sp->flags |= COL_INVERT;
break;
case 'u': /* underscore */
sp->flags |= COL_UNDERSCORE;
break;
case 'F': /* flash */
sp->flags &= ~COL_FLASH;
break;
case 'H': /* hilite */
sp->flags &= ~COL_HILITE;
break;
case 'I': /* inverse */
sp->flags &= ~COL_INVERT;
break;
case 'U': /* underscore */
sp->flags &= ~COL_UNDERSCORE;
break;
case 'b': /* blue fg */
sp->fore = COL_BLUE;
break;
case 'c': /* cyan fg */
sp->fore = COL_CYAN;
break;
case 'g': /* green fg */
sp->fore = COL_GREEN;
break;
case 'm': /* magenta fg */
sp->fore = COL_MAGENTA;
break;
case 'r': /* red fg */
sp->fore = COL_RED;
break;
case 'w': /* white fg */
sp->fore = COL_WHITE;
break;
case 'x': /* black fg */
sp->fore = COL_BLACK;
break;
case 'y': /* yellow fg */
sp->fore = COL_YELLOW;
break;
case 'B': /* blue bg */
sp->back = COL_BLUE;
break;
case 'C': /* cyan bg */
sp->back = COL_CYAN;
break;
case 'G': /* green bg */
sp->back = COL_GREEN;
break;
case 'M': /* magenta bg */
sp->back = COL_MAGENTA;
break;
case 'R': /* red bg */
sp->back = COL_RED;
break;
case 'W': /* white bg */
sp->back = COL_WHITE;
break;
case 'X': /* black bg */
sp->back = COL_BLACK;
break;
case 'Y': /* yellow bg */
sp->back = COL_YELLOW;
break;
}
}
dump_ansi_codes(sp, buff, bp);
arg1 = args[1];
process_expression(buff, bp, &arg1, executor, caller, enactor,
PE_DEFAULT, PT_DEFAULT, pe_info);
dump_ansi_codes(--sp, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_stripansi)
{
/* Strips ANSI codes away from a given string of text. Starts by
* finding the '\x' character and stripping until it hits an 'm'.
*/
char *cp;
cp = remove_markup(args[0], NULL);
safe_str(cp, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_edit)
{
int i;
char *f, *r, *raw;
ansi_string *prebuf;
char postbuf[BUFFER_LEN], lastbuf[BUFFER_LEN], *postp;
size_t rlen, flen;
prebuf = parse_ansi_string(args[0]);
raw = args[0];
for (i = 1; i < nargs - 1; i += 2) {
postp = postbuf;
f = args[i]; /* find this */
r = args[i + 1]; /* replace it with this */
flen = arglens[i];
rlen = arglens[i + 1];
/* Check for nothing to avoid infinite loop */
if (!*f && !*r)
continue;
if (flen == 1 && *f == '$') {
/* append */
safe_str(raw, postbuf, &postp);
safe_strl(r, rlen, postbuf, &postp);
} else if (flen == 1 && *f == '^') {
/* prepend */
safe_strl(r, rlen, postbuf, &postp);
safe_str(raw, postbuf, &postp);
} else if (!*f) {
/* insert between every character */
size_t last;
safe_strl(r, rlen, postbuf, &postp);
for (last = 0; last < prebuf->len; last++) {
safe_ansi_string(prebuf, last, 1, postbuf, &postp);
safe_strl(r, rlen, postbuf, &postp);
}
} else {
char *p;
size_t last = 0;
while (last < prebuf->len && (p = strstr(prebuf->text + last, f)) != NULL) {
safe_ansi_string(prebuf, last, p - (prebuf->text + last),
postbuf, &postp);
safe_strl(r, rlen, postbuf, &postp);
last = p - prebuf->text + flen;
}
if (last < prebuf->len)
safe_ansi_string(prebuf, last, prebuf->len, postbuf, &postp);
}
*postp = '\0';
free_ansi_string(prebuf);
prebuf = parse_ansi_string(postbuf);
strcpy(lastbuf, postbuf);
raw = lastbuf;
}
safe_ansi_string(prebuf, 0, prebuf->len, buff, bp);
free_ansi_string(prebuf);
}
FUNCTION(fun_brackets)
{
char *str;
int rbrack, lbrack, rbrace, lbrace, lcurl, rcurl;
lcurl = rcurl = rbrack = lbrack = rbrace = lbrace = 0;
str = args[0]; /* The string to count the brackets in */
while (*str) {
switch (*str) {
case '[':
lbrack++;
break;
case ']':
rbrack++;
break;
case '(':
lbrace++;
break;
case ')':
rbrace++;
break;
case '{':
lcurl++;
break;
case '}':
rcurl++;
break;
default:
break;
}
str++;
}
safe_format(buff, bp, "%d %d %d %d %d %d", lbrack, rbrack,
lbrace, rbrace, lcurl, rcurl);
}
/* Returns the length of str up to the first return character,
* or else the last space, or else 0.
*/
static int
wraplen(char *str, int maxlen)
{
const int length = strlen(str);
int i = 0;
if (length <= maxlen) {
/* Find the first return char
* so %r will not mess with any alignment
* functions.
*/
while (i < length) {
if ((str[i] == '\n') || (str[i] == '\r'))
return i;
i++;
}
return length;
}
/* Find the first return char
* so %r will not mess with any alignment
* functions.
*/
while (i <= maxlen + 1) {
if ((str[i] == '\n') || (str[i] == '\r'))
return i;
i++;
}
/* No return char was found. Now
* find the last space in str.
*/
while (str[maxlen] != ' ' && maxlen > 0)
maxlen--;
return (maxlen ? maxlen : -1);
}
/** The integer in string a will be stored in v,
* if a is not an integer then d (efault) is stored in v.
*/
#define initint(a, v, d) \
do \
if (arglens[a] == 0) { \
v = d; \
} else { \
if (!is_integer(args[a])) { \
safe_str(T(e_int), buff, bp); \
return; \
} \
v = parse_integer(args[a]); \
} \
while (0)
FUNCTION(fun_wrap)
{
/* args[0] = text to be wrapped (required)
* args[1] = line width (width) (required)
* args[2] = width of first line (width1st)
* args[3] = output delimiter (btwn lines)
*/
char *pstr; /* start of string */
ansi_string *as;
const char *pend; /* end of string */
int linewidth, width1st, width;
int linenr = 0;
const char *linesep;
int ansiwidth, ansilen;
if (!args[0] || !*args[0])
return;
initint(1, width, 72);
width1st = width;
if (nargs > 2)
initint(2, width1st, width);
if (nargs > 3)
linesep = args[3];
else if (NEWLINE_ONE_CHAR)
linesep = "\n";
else
linesep = "\r\n";
if (width < 2 || width1st < 2) {
safe_str(T("#-1 WIDTH TOO SMALL"), buff, bp);
return;
}
as = parse_ansi_string(args[0]);
pstr = as->text;
pend = as->text + as->len;
linewidth = width1st;
while (pstr < pend) {
if (linenr++ == 1)
linewidth = width;
if ((linenr > 1) && linesep && *linesep)
safe_str(linesep, buff, bp);
ansiwidth = ansi_strnlen(pstr, linewidth);
ansilen = wraplen(pstr, ansiwidth);
if (ansilen < 0) {
/* word doesn't fit on one line, so cut it */
safe_ansi_string2(as, pstr - as->text, ansiwidth - 1, buff, bp);
safe_chr('-', buff, bp);
pstr += ansiwidth - 1; /* move to start of next line */
} else {
/* normal line */
safe_ansi_string2(as, pstr - as->text, ansilen, buff, bp);
if (pstr[ansilen] == '\r')
++ansilen;
pstr += ansilen + 1; /* move to start of next line */
}
}
free_ansi_string(as);
}
#undef initint
#define AL_LEFT 1 /**< Align left */
#define AL_RIGHT 2 /**< Align right */
#define AL_CENTER 3 /**< Align center */
#define AL_REPEAT 4 /**< Repeat column */
static int
align_one_line(char *buff, char **bp, int ncols,
int cols[MAX_COLS], int calign[MAX_COLS], char *ptrs[MAX_COLS],
ansi_string *as[MAX_COLS],
int linenum, char *fieldsep, int fslen,
char *linesep, int lslen, char filler)
{
static char line[BUFFER_LEN];
static char segment[BUFFER_LEN];
char *sp;
char *ptr, *tptr;
char *lp;
char *lastspace;
int i, j;
int len;
int cols_done;
int skipspace;
lp = line;
memset(line, filler, BUFFER_LEN);
cols_done = 0;
for (i = 0; i < ncols; i++) {
if (!ptrs[i] || !*ptrs[i]) {
if (calign[i] & AL_REPEAT) {
ptrs[i] = as[i]->text;
} else {
lp += cols[i];
if (i < (ncols - 1) && fslen)
safe_str(fieldsep, line, &lp);
cols_done++;
continue;
}
}
if (calign[i] & AL_REPEAT) {
cols_done++;
}
for (len = 0, ptr = ptrs[i], lastspace = NULL; len < cols[i]; ptr++, len++) {
if ((!*ptr) || (*ptr == '\n'))
break;
if (isspace((unsigned char) *ptr)) {
lastspace = ptr;
}
}
skipspace = 0;
sp = segment;
if (!*ptr) {
if (len > 0) {
safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
ptrs[i] = ptr;
} else if (*ptr == '\n') {
for (tptr = ptr;
*tptr && tptr >= ptrs[i] && isspace((unsigned char) *tptr); tptr--) ;
len = (tptr - ptrs[i]) + 1;
if (len > 0) {
safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
ptrs[i] = ptr + 1;
ptr = tptr;
} else if (lastspace) {
ptr = lastspace;
skipspace = 1;
for (tptr = ptr;
*tptr && tptr >= ptrs[i] && isspace((unsigned char) *tptr); tptr--) ;
len = (tptr - ptrs[i]) + 1;
if (len > 0) {
safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
ptrs[i] = lastspace;
} else {
if (len > 0) {
safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
ptrs[i] = ptr;
}
*sp = '\0';
if ((calign[i] & 3) == AL_LEFT) {
safe_str(segment, line, &lp);
lp += cols[i] - len;
} else if ((calign[i] & 3) == AL_RIGHT) {
lp += cols[i] - len;
safe_str(segment, line, &lp);
} else if ((calign[i] & 3) == AL_CENTER) {
j = cols[i] - len;
lp += j >> 1;
safe_str(segment, line, &lp);
lp += (j >> 1) + (j & 1);
}
if ((lp - line) > BUFFER_LEN)
lp = (line + BUFFER_LEN - 1);
if (i < (ncols - 1) && fslen)
safe_str(fieldsep, line, &lp);
if (skipspace)
for (;
*ptrs[i] && (*ptrs[i] != '\n') && isspace((unsigned char) *ptrs[i]);
ptrs[i]++) ;
}
if (cols_done == ncols)
return 0;
*lp = '\0';
if (linenum > 0 && lslen > 0)
safe_str(linesep, buff, bp);
safe_str(line, buff, bp);
return 1;
}
FUNCTION(fun_align)
{
int nline;
char *ptr;
int ncols;
int i;
static int cols[MAX_COLS];
static int calign[MAX_COLS];
static ansi_string *as[MAX_COLS];
static char *ptrs[MAX_COLS];
char filler;
char *fieldsep;
int fslen;
char *linesep;
int lslen;
filler = ' ';
fieldsep = (char *) " ";
linesep = (char *) "\n";
/* Get column widths */
ncols = 0;
for (ptr = args[0]; *ptr; ptr++) {
while (isspace((unsigned char) *ptr))
ptr++;
if (*ptr == '>') {
calign[ncols] = AL_RIGHT;
ptr++;
} else if (*ptr == '-') {
calign[ncols] = AL_CENTER;
ptr++;
} else if (*ptr == '<') {
calign[ncols] = AL_LEFT;
ptr++;
} else if (isdigit((unsigned char) *ptr)) {
calign[ncols] = AL_LEFT;
} else {
safe_str(T("#-1 INVALID ALIGN STRING"), buff, bp);
return;
}
for (i = 0; *ptr && isdigit((unsigned char) *ptr); ptr++) {
i *= 10;
i += *ptr - '0';
}
if (*ptr == '.') {
calign[ncols] |= AL_REPEAT;
ptr++;
}
cols[ncols++] = i;
if (!*ptr)
break;
}
for (i = 0; i < ncols; i++) {
if (cols[i] < 1) {
safe_str(T("#-1 CANNOT HAVE COLUMN OF SIZE 0"), buff, bp);
return;
}
}
if (ncols < 1) {
safe_str(T("#-1 NOT ENOUGH COLUMNS FOR ALIGN"), buff, bp);
return;
}
if (ncols > MAX_COLS) {
safe_str(T("#-1 TOO MANY COLUMNS FOR ALIGN"), buff, bp);
return;
}
if (nargs < (ncols + 1) || nargs > (ncols + 4)) {
safe_str(T("#-1 INVALID NUMBER OF ARGUMENTS TO ALIGN"), buff, bp);
return;
}
if (nargs >= (ncols + 2)) {
if (!args[ncols + 1] || strlen(args[ncols + 1]) != 1) {
safe_str(T("#-1 FILLER MUST BE ONE CHARACTER"), buff, bp);
return;
}
filler = *(args[ncols + 1]);
}
if (nargs >= (ncols + 3)) {
fieldsep = args[ncols + 2];
}
if (nargs >= (ncols + 4)) {
linesep = args[ncols + 3];
}
fslen = strlen(fieldsep);
lslen = strlen(linesep);
for (i = 0; i < MAX_COLS; i++) {
as[i] = NULL;
}
for (i = 0; i < ncols; i++) {
as[i] = parse_ansi_string(args[i + 1]);
ptrs[i] = as[i]->text;
}
nline = 0;
while (1) {
if (!align_one_line(buff, bp, ncols, cols, calign, ptrs,
as, nline++, fieldsep, fslen, linesep, lslen, filler))
break;
}
**bp = '\0';
for (i = 0; i < ncols; i++) {
free_ansi_string(as[i]);
ptrs[i] = as[i]->text;
}
return;
}