pennmush/src/funstr.c
Shawn 61ae17eea9 Revert "Shrink the size of the ansi_string struct"
This reverts commit 2fca82944d.

It's causing way too many subtle problems. Revist later.
2018-04-04 18:36:14 -07:00

2349 lines
55 KiB
C

/**
* \file funstr.c
*
* \brief String functions for mushcode.
*
*
*/
#include "copyrite.h"
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <locale.h>
#include <stddef.h>
#include "ansi.h"
#include "attrib.h"
#include "case.h"
#include "conf.h"
#include "dbdefs.h"
#include "externs.h"
#include "flags.h"
#include "htab.h"
#include "lock.h"
#include "markup.h"
#include "match.h"
#include "mushdb.h"
#include "notify.h"
#include "parse.h"
#include "pueblo.h"
#include "sort.h"
#include "strutil.h"
#ifdef WIN32
#pragma warning(disable : 4761) /* NJG: disable warning re conversion */
#endif
#define MAX_COLS 32 /**< Maximum number of columns for align() */
static int wraplen(char *str, size_t 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], ansi_data adata[MAX_COLS],
int linenum, char *fieldsep, int fslen, char *linesep,
int lslen, char filler);
static int comp_gencomp(dbref executor, char *left, char *right,
const char *type);
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 LC_MESSAGES
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(*p)) {
safe_chr('0', buff, bp);
return;
}
}
safe_chr('1', buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_capstr)
{
char *p = args[0];
WALK_ANSI_STRING(p)
{
*p = UPCASE(*p);
break;
}
safe_strl(args[0], arglens[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_art)
{
/* checks a word and returns the appropriate article, "a" or "an" */
char c;
if (!*args[0]) {
safe_chr('a', buff, bp);
return;
}
c = DOWNCASE(*args[0]);
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
safe_strl("an", 2, 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)
{
int j, m = 0;
for (j = 1; j < nargs; j++) {
if (strcoll(args[m], args[j]) < 0) {
m = j;
}
}
safe_strl(args[m], arglens[m], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_alphamin)
{
int j, m = 0;
for (j = 1; j < nargs; j++) {
if (strcoll(args[m], args[j]) > 0) {
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) {
safe_str(T(e_range), buff, bp);
free_ansi_string(as);
return;
}
if (len < 0) {
pos = pos + len + 1;
if (pos < 0)
pos = 0;
len = -len;
}
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 (len > as->len)
safe_strl(args[0], arglens[0], buff, bp);
else
safe_ansi_string(as, as->len - len, 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 (pos > as->len || num == 0) {
safe_strl(args[0], arglens[0], buff, bp);
free_ansi_string(as);
return;
}
if (num < 0) {
pos += num + 1;
if (pos < 0)
pos = 0;
}
ansi_string_delete(as, pos, num);
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_str_rep_or_ins)
{
ansi_string *dst, *src;
int start = 0, len = 0;
int srcarg;
bool inserting = 1;
if (!is_integer(args[1])) {
safe_str(T(e_ints), buff, bp);
return;
}
start = parse_integer(args[1]);
if (start < 0) {
safe_str(T(e_argrange), buff, bp);
return;
}
if (!strcmp(called_as, "STRREPLACE")) {
/* strreplace - arg 2 is a length, arg
* 3 is the string to add */
if (!is_integer(args[2])) {
safe_str(T(e_ints), buff, bp);
return;
}
len = parse_integer(args[2]);
if (len < 0) {
safe_str(T(e_argrange), buff, bp);
return;
}
srcarg = 3;
inserting = 0;
} else {
/* strinsert() has no length, arg 2 is
* the string to add */
srcarg = 2;
}
dst = parse_ansi_string(args[0]);
if (start > dst->len) {
safe_strl(args[0], arglens[0], buff, bp);
if (inserting)
safe_strl(args[srcarg], arglens[srcarg], buff, bp);
free_ansi_string(dst);
return;
}
src = parse_ansi_string(args[srcarg]);
ansi_string_replace(dst, start, len, src);
safe_ansi_string(dst, 0, dst->len, buff, bp);
free_ansi_string(dst);
free_ansi_string(src);
}
extern int sort_order; /* from sort.c */
static int
comp_gencomp(dbref executor, char *left, char *right, const char *type)
{
int c;
c = gencomp(executor, left, right, type);
return (c > 0 ? 1 : (c < 0 ? -1 : 0)) * sort_order;
}
/* 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 = UPCASE(*args[2]);
}
switch (type) {
case 'A': /* Case-sensitive lexicographic */
{
safe_integer(comp_gencomp(executor, args[0], args[1], ALPHANUM_LIST), buff,
bp);
return;
}
case 'I': /* Case-insensitive lexicographic */
{
safe_integer(comp_gencomp(executor, args[0], args[1], 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 *pos;
pos = strstr(args[1], args[0]);
if (pos)
safe_integer(pos - args[1] + 1, buff, bp);
else
safe_str("#-1", buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_lpos)
{
char c = ' ';
int first = 1, n;
if (args[1][0])
c = args[1][0];
for (n = 0; n < arglens[0]; n++)
if (args[0][n] == c) {
if (first)
first = 0;
else
safe_chr(' ', buff, bp);
safe_integer(n, buff, bp);
}
}
/* ARGSUSED */
FUNCTION(fun_strmatch)
{
char *ret[36];
int matches;
int i;
char *qregs[NUMQ];
int nqregs;
char match_space[BUFFER_LEN * 2];
ssize_t match_space_len = BUFFER_LEN * 2;
/* matches a wildcard pattern for an _entire_ string */
if (nargs > 2) {
matches = wild_match_case_r(args[1], args[0], 0, ret, NUMQ, match_space,
match_space_len, NULL, 0);
safe_boolean(matches, buff, bp);
if (matches) {
/* Now, assign the captures, if we have returns. */
nqregs = list2arr(qregs, NUMQ, args[2], ' ', 0);
for (i = 0; i < nqregs; i++) {
if (ValidQregName(qregs[i])) {
PE_Setq(pe_info, qregs[i], ret[i]);
} else if (qregs[i][0] != '-' || qregs[i][1]) {
safe_str(T(e_badregname), buff, bp);
}
}
}
} else {
matches = wild_match_case_r(args[1], args[0], 0, NULL, 0, NULL, 0, NULL, 0);
safe_boolean(matches, 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;
as = parse_ansi_string(args[0]);
flip_ansi_string(as);
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.
*/
int i, j;
size_t len;
char *ptr = args[0];
char matched[UCHAR_MAX + 1] = {'\0'};
ansi_string *as;
/* find the characters to look for */
if (!args[2] || !*args[2])
matched[' '] = 1;
else {
char *p;
for (p = remove_markup(args[2], &len); p && *p; p++)
matched[*p] = 1;
}
as = parse_ansi_string(args[1]);
/* do length checks first */
if (as->len != ansi_strlen(ptr)) {
safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
free_ansi_string(as);
return;
}
/* walk strings, copy from the appropriate string */
i = 0;
ptr = args[0];
while (*ptr) {
switch (*ptr) {
case ESC_CHAR:
while (*ptr && *ptr != 'm') {
safe_chr(*(ptr++), buff, bp);
}
safe_chr(*(ptr++), buff, bp);
break;
case TAG_START:
case TAG_END:
while (*ptr && *ptr != TAG_END) {
safe_chr(*(ptr++), buff, bp);
}
safe_chr(*(ptr++), buff, bp);
break;
default:
if (matched[*ptr]) {
j = 0;
while (*ptr && matched[*ptr]) {
ptr++;
j++;
}
if (j != 0) {
safe_ansi_string(as, i, j, buff, bp);
i += j;
}
} else {
i++;
safe_chr(*(ptr++), buff, bp);
}
}
}
free_ansi_string(as);
}
/* 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 *ip, *op;
size_t i, len;
char cur, dest;
char *c;
ansi_string *as;
/* Initialize */
for (i = 0; i < 256; i++) {
charmap[i] = (char) i;
}
#define goodchr(x) (isprint(x) || (x == '\n'))
/* Convert ranges in input string, and check that
* we don't receive a nonprinting char such as
* beep() */
ip = instr;
c = remove_markup(args[1], NULL);
while (*c) {
cur = *c;
if (!goodchr(cur)) {
safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
return;
}
/* Tack it onto the string */
/* Do we have a range? */
if (*(c + 1) && *(c + 1) == '-' && *(c + 2)) {
dest = *(c + 2);
if (!goodchr(dest)) {
safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
return;
}
if (dest > cur) {
for (; cur <= dest; cur++) {
if (goodchr(cur))
safe_chr((char) cur, instr, &ip);
}
} else {
for (; cur >= dest; cur--) {
if (goodchr(cur))
safe_chr((char) cur, instr, &ip);
}
}
c += 3;
} else {
safe_chr((char) cur, instr, &ip);
c++;
}
}
*ip = '\0';
/* Convert ranges in output string, and check that
* we don't receive a nonprinting char such as
* beep() */
op = outstr;
c = remove_markup(args[2], NULL);
while (*c) {
cur = *c;
if (!goodchr(cur)) {
safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
return;
}
/* Tack it onto the string */
/* Do we have a range? */
if (*(c + 1) && *(c + 1) == '-' && *(c + 2)) {
dest = *(c + 2);
if (!goodchr(dest)) {
safe_str(T("#-1 TR CANNOT ACCEPT NONPRINTING CHARS"), buff, bp);
return;
}
if (dest > cur) {
for (; cur <= dest; cur++) {
if (goodchr(cur))
safe_chr((char) cur, outstr, &op);
}
} else {
for (; cur >= dest; cur--) {
if (goodchr(cur))
safe_chr((char) cur, outstr, &op);
}
}
c += 3;
} else {
safe_chr((char) cur, outstr, &op);
c++;
}
}
*op = '\0';
#undef goodchr
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[instr[i]] = outstr[i];
/* walk the string, translating characters */
as = parse_ansi_string(args[0]);
len = as->len;
for (i = 0; i < len; i++) {
as->text[i] = charmap[as->text[i]];
}
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_lcstr)
{
char *p;
p = args[0];
WALK_ANSI_STRING(p)
{
*p = DOWNCASE(*p);
p++;
}
safe_str(args[0], buff, bp);
return;
}
/* ARGSUSED */
FUNCTION(fun_ucstr)
{
char *p;
p = args[0];
WALK_ANSI_STRING(p)
{
*p = UPCASE(*p);
p++;
}
safe_str(args[0], buff, bp);
return;
}
/* 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)) {
char *ts, *te;
ts = strrchr(buff, TAG_START);
te = strrchr(buff, TAG_END);
if (ts && te && ts > te) {
*ts = '\0';
bp = &ts;
}
break;
}
}
times = times >> 1;
if (safe_str(args[0], args[0], &ap)) {
char *ts, *te;
*ap = '\0';
ts = strrchr(args[0], TAG_START);
te = strrchr(args[0], TAG_END);
if (times > 1)
times = 1;
if (ts && te && ts > te)
*ts = '\0';
} else {
*ap = '\0';
}
arglens[0] = strlen(args[0]);
}
}
/* ARGSUSED */
FUNCTION(fun_scramble)
{
ansi_string *as;
if (!*args[0])
return;
as = parse_ansi_string(args[0]);
scramble_ansi_string(as);
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;
ansi_string *as;
int fillq, fillr, i;
char fillstr[BUFFER_LEN], *fp;
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) {
/* Check to see if we should truncate */
if (nargs > 3 && parse_boolean(args[3])) {
if (has_markup(args[0])) {
as = parse_ansi_string(args[0]);
safe_ansi_string(as, 0, spaces, buff, bp);
free_ansi_string(as);
} else {
safe_strl(args[0], spaces, buff, bp);
}
} else {
safe_strl(args[0], arglens[0], buff, bp);
}
return;
}
spaces -= len;
if (!args[2] || !*args[2]) {
/* Fill with spaces */
safe_strl(args[0], arglens[0], buff, bp);
safe_fill(' ', spaces, buff, bp);
return;
}
len = ansi_strlen(args[2]);
if (!len) {
safe_str(T("#-1 FILL ARGUMENT MAY NOT BE ZERO-LENGTH"), buff, bp);
return;
}
safe_strl(args[0], arglens[0], buff, bp);
as = parse_ansi_string(args[2]);
fillq = spaces / len;
fillr = spaces % len;
fp = fillstr;
for (i = 0; i < fillq; i++)
safe_ansi_string(as, 0, as->len, fillstr, &fp);
safe_ansi_string(as, 0, fillr, fillstr, &fp);
*fp = '\0';
free_ansi_string(as);
safe_str(fillstr, buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_rjust)
{
/* pads a string with leading blanks (or other fill character) */
size_t spaces, len;
ansi_string *as;
int fillq, fillr, i;
char fillstr[BUFFER_LEN], *fp;
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) {
/* Check to see if we should truncate */
if (nargs > 3 && parse_boolean(args[3])) {
if (has_markup(args[0])) {
as = parse_ansi_string(args[0]);
safe_ansi_string(as, 0, spaces, buff, bp);
free_ansi_string(as);
} else {
safe_strl(args[0], spaces, buff, bp);
}
} else {
safe_strl(args[0], arglens[0], buff, bp);
}
return;
}
spaces -= len;
if (!args[2] || !*args[2]) {
/* Fill with spaces */
safe_fill(' ', spaces, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
return;
}
len = ansi_strlen(args[2]);
if (!len) {
safe_str(T("#-1 FILL ARGUMENT MAY NOT BE ZERO-LENGTH"), buff, bp);
return;
}
as = parse_ansi_string(args[2]);
fillq = spaces / len;
fillr = spaces % len;
fp = fillstr;
for (i = 0; i < fillq; i++)
safe_ansi_string(as, 0, as->len, fillstr, &fp);
safe_ansi_string(as, 0, fillr, fillstr, &fp);
*fp = '\0';
free_ansi_string(as);
safe_str(fillstr, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_center)
{
/* pads a string with leading blanks (or other fill string) */
size_t width, len, lsp, rsp, filllen;
int fillq, fillr, i;
char fillstr[BUFFER_LEN], *fp;
ansi_string *as;
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;
}
lsp = rsp = (width - len) / 2;
rsp += (width - len) % 2;
if (lsp >= BUFFER_LEN)
lsp = rsp = BUFFER_LEN - 1;
if ((!args[2] || !*args[2]) && (!args[3] || !*args[3])) {
/* Fast case for default fill with spaces */
safe_fill(' ', lsp, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
safe_fill(' ', rsp, buff, bp);
return;
}
/* args[2] contains the possibly ansi, multi-char fill string */
filllen = ansi_strlen(args[2]);
if (!filllen) {
safe_str(T("#-1 FILL ARGUMENT MAY NOT BE ZERO-LENGTH"), buff, bp);
return;
}
as = parse_ansi_string(args[2]);
fillq = lsp / filllen;
fillr = lsp % filllen;
fp = fillstr;
for (i = 0; i < fillq; i++)
safe_ansi_string(as, 0, as->len, fillstr, &fp);
safe_ansi_string(as, 0, fillr, fillstr, &fp);
*fp = '\0';
free_ansi_string(as);
safe_str(fillstr, buff, bp);
safe_strl(args[0], arglens[0], buff, bp);
/* If we have args[3], that's the right-side fill string */
if (nargs > 3) {
if (args[3] && *args[3]) {
filllen = ansi_strlen(args[3]);
if (!filllen) {
safe_str(T("#-1 FILL ARGUMENT MAY NOT BE ZERO-LENGTH"), buff, bp);
return;
}
as = parse_ansi_string(args[3]);
fillq = rsp / filllen;
fillr = rsp % filllen;
fp = fillstr;
for (i = 0; i < fillq; i++)
safe_ansi_string(as, 0, as->len, fillstr, &fp);
safe_ansi_string(as, 0, fillr, fillstr, &fp);
*fp = '\0';
free_ansi_string(as);
safe_str(fillstr, buff, bp);
} else {
/* Null args[3], fill right side with spaces */
safe_fill(' ', rsp, buff, bp);
}
return;
}
/* No args[3], so we flip args[2] */
filllen = ansi_strlen(args[2]);
as = parse_ansi_string(args[2]);
fillq = rsp / filllen;
fillr = rsp % filllen;
fp = fillstr;
for (i = 0; i < fillq; i++)
safe_ansi_string(as, 0, as->len, fillstr, &fp);
safe_ansi_string(as, 0, fillr, fillstr, &fp);
*fp = '\0';
free_ansi_string(as);
as = parse_ansi_string(fillstr);
flip_ansi_string(as);
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* 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.
*/
ansi_string *as;
char *lp;
char cbuf[BUFFER_LEN], *cp;
PE_REGS *pe_regs;
int placenr = 0;
int funccount;
char *oldbp;
char start, end;
char result[BUFFER_LEN];
char placestr[10];
ufun_attrib ufun;
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';
}
if (!fetch_ufun_attrib(args[0], executor, &ufun,
UFUN_DEFAULT | UFUN_REQUIRE_ATTR))
return;
as = parse_ansi_string(args[1]);
lp = as->text;
if (nargs >= 3) {
lp = strchr(as->text, start);
if (!lp) {
safe_str(args[1], buff, bp);
free_ansi_string(as);
return;
}
safe_ansi_string(as, 0, (lp++ - as->text), buff, bp);
placenr = (lp - as->text);
}
cbuf[1] = '\0';
oldbp = *bp;
funccount = pe_info->fun_invocations;
pe_regs = pe_regs_create(PE_REGS_ARG, "fun_foreach");
pe_regs_setenv_nocopy(pe_regs, 0, cbuf);
pe_regs_setenv_nocopy(pe_regs, 1, placestr);
while (*lp) {
if (*lp == end) {
lp++;
break;
}
/* Set env */
cp = cbuf;
safe_ansi_string(as, (lp - as->text), 1, cbuf, &cp);
*cp = '\0';
snprintf(placestr, 10, "%d", placenr++);
lp++;
if (call_ufun(&ufun, result, executor, enactor, pe_info, pe_regs))
break;
safe_str(result, buff, bp);
/* Make sure we're actually doing stuff. */
if (*bp == oldbp && pe_info->fun_invocations == funccount)
break;
oldbp = *bp;
funccount = pe_info->fun_invocations;
}
placenr++;
if (*lp)
safe_ansi_string(as, (lp - as->text), as->len, buff, bp);
pe_regs_free(pe_regs);
free_ansi_string(as);
}
extern char escaped_chars[UCHAR_MAX + 1];
/* ARGSUSED */
FUNCTION(fun_decompose)
{
/* This function simply returns a decompose'd version of
* the included string, such that
* s(decompose(str)) == str, down to the last space, tab,
* and newline. */
safe_decompose_str(args[0], buff, bp);
}
/* 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
*/
char *p;
for (p = args[0]; *p; p++)
if (escaped_chars[*p])
*p = ' ';
safe_strl(args[0], arglens[0], buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_escape)
{
char *s;
if (arglens[0]) {
safe_chr('\\', buff, bp);
for (s = args[0]; *s; s++) {
if ((s != args[0]) && escaped_chars[*s])
safe_chr('\\', buff, bp);
safe_chr((char) *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.
*/
enum trim_style { TRIM_LEFT, TRIM_RIGHT, TRIM_BOTH } trim;
int trim_style_arg, trim_char_arg;
ansi_string *as;
int s, e;
char *delims;
char totrim[0x100] = {'\0'};
/* 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 = 1;
trim_char_arg = 2;
} else if (!strcmp(called_as, "TRIMPENN")) {
trim_style_arg = 2;
trim_char_arg = 1;
} else if (TINY_TRIM_FUN) {
trim_style_arg = 1;
trim_char_arg = 2;
} else {
trim_style_arg = 2;
trim_char_arg = 1;
}
/* If a trim style is provided, it must be the third argument. */
if (nargs > trim_style_arg) {
switch (*args[trim_style_arg]) {
case 'l':
case 'L':
trim = TRIM_LEFT;
break;
case 'r':
case 'R':
trim = TRIM_RIGHT;
break;
default:
trim = TRIM_BOTH;
break;
}
} else
trim = TRIM_BOTH;
if (nargs > trim_char_arg && args[trim_char_arg] && *args[trim_char_arg]) {
delims = args[trim_char_arg];
while (*delims) {
totrim[*delims] = 1;
delims++;
}
} else {
totrim[' '] = 1;
}
/* 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.
*/
as = parse_ansi_string(args[0]);
s = 0;
e = as->len;
if (trim != TRIM_LEFT) {
while (e > 0 && totrim[as->text[e - 1]])
e--;
}
if (trim != TRIM_RIGHT) {
while (s < e && totrim[as->text[s]])
s++;
}
safe_ansi_string(as, s, e - s, buff, bp);
free_ansi_string(as);
}
/* 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.
*/
int i, j;
char sep;
int insep = 1;
ansi_string *as;
/* Figure out the character to squish */
if (!delim_check(buff, bp, nargs, args, 2, &sep))
return;
as = parse_ansi_string(args[0]);
/* get rid of trailing spaces first, so we don't have to worry about
* them later.
*/
for (i = as->len - 1; i >= 0; i--) {
if (as->text[i] != sep)
break;
}
as->len = i + 1;
/* Now trim leading and sequences */
for (i = 0, j = 0; i < as->len; i++) {
if (as->text[i] == sep) {
if (insep)
continue;
insep = 1;
} else {
insep = 0;
}
if (i != j) {
as->text[j] = as->text[i];
if (as->markup) {
as->markup[j] = as->markup[i];
}
}
j++;
}
as->len = j;
as->text[j] = '\0';
safe_ansi_string(as, 0, as->len, buff, bp);
free_ansi_string(as);
}
/* ARGSUSED */
FUNCTION(fun_space)
{
size_t s;
if (!is_strict_uinteger(args[0])) {
safe_str(T(e_uint), buff, bp);
return;
}
s = parse_uinteger(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 ((k <= 0) || (k > 5)) {
safe_str(T(e_range), buff, bp);
return;
}
safe_fill(BEEP_CHAR, k, buff, bp);
}
FUNCTION(fun_ord)
{
int c;
if (!args[0] || !args[0][0] || arglens[0] != 1) {
safe_str(T("#-1 FUNCTION (ORD) EXPECTS ONE CHARACTER"), buff, bp);
return;
}
c = args[0][0];
if (isprint(c)) {
safe_integer(c, buff, bp);
} else {
safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);
}
}
FUNCTION(fun_chr)
{
int c;
if (!is_strict_uinteger(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[args[0][n]].base)
safe_str(accent_table[args[0][n]].base, buff, bp);
else
safe_chr(args[0][n], buff, bp);
}
}
/* ARGSUSED */
FUNCTION(fun_edit)
{
int i, j;
char *needle;
size_t nlen;
char *search, *ptr;
ansi_string *orig, *repl;
orig = parse_ansi_string(args[0]);
for (i = 1; i < nargs - 1; i += 2) {
needle = remove_markup(args[i], &nlen);
nlen--;
repl = parse_ansi_string(args[i + 1]);
if (strcmp(needle, "$") == 0) {
ansi_string_insert(orig, orig->len, repl);
} else if (strcmp(needle, "^") == 0) {
ansi_string_insert(orig, 0, repl);
} else if (nlen == 0) {
/* Annoying. Stick repl between each character */
/* Since this is inserts, we're working *backwards* */
for (j = orig->len - 1; j > 0; j--) {
ansi_string_insert(orig, j, repl);
}
} else {
search = orig->text;
/* Find each occurrence */
while ((ptr = strstr(search, needle)) != NULL) {
if ((ptr - orig->text) > orig->len)
break;
/* Perform the replacement */
if (ansi_string_replace(orig, ptr - orig->text, nlen, repl))
break;
search = ptr + repl->len;
}
}
free_ansi_string(repl);
}
safe_ansi_string(orig, 0, orig->len, buff, bp);
free_ansi_string(orig);
}
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 -1.
*/
static int
wraplen(char *str, size_t maxlen)
{
size_t i, last = -1;
/* If there's a newline in the first maxlen chars, return its position.
* Otherwise, return the whole text if it's shorter than maxlen,
* the position of the last space before maxlen (if there is one) or,
* if all else fails, -1 to linewrap a word, hyphenated. */
for (i = 0; i < maxlen; i++) {
if (!str[i])
return i;
else if ((str[i] == '\n') || (str[i] == '\r'))
return i;
else if (str[i] == ' ')
last = i;
}
/* Check to make sure the text wasn't hard-wrapped at the end of
* this line anyway */
if ((str[i] == '\n') || (str[i] == '\r') || (str[i] == '\0'))
return i;
return last;
}
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 (linesep) (btwn lines)
*/
char *pstr; /* start of string */
ansi_string *as;
ansi_string *hyphen = NULL;
const char *pend; /* end of string */
int linewidth, width1st, width;
int linenr = 0;
const char *linesep;
int ansilen;
if (!args[0] || !*args[0])
return;
if (ansi_strlen(args[0]) == 0) {
safe_str(args[0], buff, bp);
return;
}
if (!int_check(buff, bp, nargs, args, 2, &width, 72))
return;
if (!int_check(buff, bp, nargs, args, 3, &width1st, width))
return;
if (width1st == 0)
width1st = width;
if (nargs > 3)
linesep = args[3];
else
linesep = "\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);
ansilen = wraplen(pstr, linewidth);
if (ansilen < 0) {
/* word doesn't fit on one line, so cut it */
if (hyphen == NULL)
hyphen = parse_ansi_string("-");
if (!ansi_string_insert(as, pstr - as->text + linewidth - 1, hyphen))
pend++;
safe_ansi_string(as, pstr - as->text, linewidth, buff, bp);
pstr += linewidth; /* move to start of next line */
} else {
/* normal line */
safe_ansi_string(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);
if (hyphen)
free_ansi_string(hyphen);
}
/* Alignment types. */
#define AL_LEFT 1 /**< Align left (<) */
#define AL_RIGHT 2 /**< Align right (>) */
#define AL_CENTER 3 /**< Align center (-) */
#define AL_FULL 4 /**< Full justify (_) */
#define AL_WPFULL 5 /**< Paragraph full-justify (=) */
#define AL_TYPE 0x0F /**< Only the bottom 4 bits are used for the type. */
/* Flags */
#define AL_REPEAT 0x100 /**< Repeat column (.) */
#define AL_COALESCE_LEFT \
0x200 /**< Coalesce empty column with column to left (`) */
#define AL_COALESCE_RIGHT \
0x400 /**< Coalesce empty column with column to right (') */
#define AL_NOFILL 0x800 /**< No filler on the right of this. ($) */
#define AL_TRUNC_EACH \
0x1000 /**< Truncate each (%r-separated) line instead of wrapping (x) */
#define AL_TRUNC_ALL \
0x2000 /**< Truncate the entire column output, at column width or %r, \
whichever comes first (X) */
#define AL_NOCOLSEP \
0x4000 /**< Don't add a column separator after this 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], ansi_data adata[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;
char *lp;
char *lastspace;
int i, j, k;
int spacesneeded, numspaces, spacecount;
int iswpfull;
int len;
int cols_done;
int skipspace;
bool needsep = 0;
lp = line;
memset(line, filler, BUFFER_LEN);
cols_done = 0;
for (i = 0; i < ncols; i++) {
/* Skip 0-width and negative columns */
if (cols[i] <= 0) {
cols_done++;
continue;
}
/* Is the next column AL_COALESCE_LEFT and has it run out of
* text? If so, do the coalesce now. First, find the next column we
* haven't already coalesced.
*/
for (j = i + 1; j < ncols; j++) {
if (cols[j] > 0)
break;
}
if ((j < ncols) &&
(!(calign[j] & AL_REPEAT) && (calign[j] & AL_COALESCE_LEFT) &&
(!ptrs[j] || !*ptrs[j]))) {
/* To coalesce left on this line, modify the left column's
* width and set the current column width to 0 (which we can
* teach it to skip). */
/* If the next column is marked NOFILL or NOCOLSEP, we inherit those. */
calign[i] |= (calign[j] & (AL_NOFILL | AL_NOCOLSEP));
cols[i] += cols[j] + fslen;
cols[j] = 0;
}
if (!ptrs[i] || !*ptrs[i]) {
if (calign[i] & AL_REPEAT) {
ptrs[i] = as[i]->text;
} else {
if (calign[i] & AL_COALESCE_RIGHT) {
/* To coalesce right on this line,
* modify the current column's width to 0, modify the right
* column's width, and continue on to processing the next
* column
*/
for (j = i + 1; j < ncols; j++) {
if (cols[j] > 0) {
cols[j] += cols[i] + fslen;
cols[i] = 0;
break;
}
}
if (cols[i] > 0) {
/* We didn't have a column to coalesce with */
calign[i] &= ~AL_COALESCE_RIGHT;
} else {
cols_done++;
continue;
}
}
if (needsep && fslen)
safe_str(fieldsep, line, &lp);
needsep = !(calign[i] & AL_NOCOLSEP);
if (!(calign[i] & AL_NOFILL)) {
if (HAS_ANSI(adata[i])) {
write_ansi_data(&adata[i], line, &lp);
}
lp += cols[i];
if (HAS_ANSI(adata[i])) {
write_ansi_close(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(*ptr)) {
lastspace = ptr;
}
}
/* Fixes align(3,123 1 1 1 1) */
if (isspace((int) *ptr)) {
lastspace = ptr;
}
skipspace = 0;
sp = segment;
if (!*ptr) {
if (len > 0) {
safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
ptrs[i] = ptr;
} else if (*ptr == '\n') {
if (len > 0) {
safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
if (calign[i] & AL_TRUNC_ALL)
ptrs[i] = strchr(ptr, '\0');
else
ptrs[i] = ptr + 1;
} else if (lastspace && !(calign[i] & (AL_TRUNC_ALL | AL_TRUNC_EACH))) {
char *tptr;
ptr = lastspace;
skipspace = 1;
for (tptr = ptr; *tptr && tptr >= ptrs[i] && isspace(*tptr); tptr--)
;
len = (tptr - ptrs[i]) + 1;
if (len > 0) {
safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
ptrs[i] = lastspace;
} else {
if (len > 0) {
safe_ansi_string(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
}
if (calign[i] & AL_TRUNC_ALL)
ptrs[i] = strchr(ptr, '\0');
else if (calign[i] & AL_TRUNC_EACH) {
char *tptr = strchr(ptr, '\n');
if (tptr == NULL)
ptrs[i] = strchr(ptr, '\0');
else
ptrs[i] = tptr + 1;
} else
ptrs[i] = ptr;
}
*sp = '\0';
if (needsep && fslen)
safe_str(fieldsep, line, &lp);
needsep = !(calign[i] & AL_NOCOLSEP);
if (HAS_ANSI(adata[i])) {
write_ansi_data(&adata[i], line, &lp);
}
switch (calign[i] & AL_TYPE) {
case AL_FULL:
case AL_WPFULL:
/* This is stupid: If it's full justify and not a hard break, then
* we stretch spaces. If it is a hard break, then we fall through
* to left-align.
*/
iswpfull = (calign[i] & AL_TYPE) == AL_WPFULL;
/* For a word processor full justify, # of spaces needed needs to be
* less than half of the lenth.
*/
spacesneeded = cols[i] - len;
numspaces = 0;
for (j = 0; segment[j]; j++) {
if (isspace((int) segment[j])) {
numspaces++;
}
}
if (spacesneeded > 0 && (!iswpfull || (cols[i] / spacesneeded) >= 2) &&
numspaces > 0) {
spacecount = 0;
for (j = 0; segment[j]; j++) {
/* Copy the char over. */
safe_chr(segment[j], line, &lp);
/* If it's a space, expand it. */
if (isspace((int) segment[j])) {
k = (spacesneeded / numspaces);
if (spacecount < (spacesneeded % numspaces)) {
k++;
spacecount++;
}
for (; k > 0; k--) {
safe_chr(segment[j], line, &lp);
}
}
}
break;
}
/* FALLTHRU */
default: /* Left-align */
safe_str(segment, line, &lp);
/* Don't fill if we're set NOFILL */
if (!(calign[i] & AL_NOFILL)) {
lp += cols[i] - len;
}
break;
case AL_RIGHT:
/* Don't fill if we're set NOFILL and this is an empty line. */
if (*segment || !(calign[i] & AL_NOFILL)) {
lp += cols[i] - len;
}
safe_str(segment, line, &lp);
break;
case AL_CENTER:
j = cols[i] - len;
lp += j >> 1;
safe_str(segment, line, &lp);
/* Don't fill if we're set NOFILL */
if (!(calign[i] & AL_NOFILL)) {
lp += (j >> 1) + (j & 1);
}
break;
}
if (HAS_ANSI(adata[i])) {
write_ansi_close(line, &lp);
}
if ((lp - line) > BUFFER_LEN)
lp = (line + BUFFER_LEN - 1);
if (skipspace)
for (; *ptrs[i] && (*ptrs[i] != '\n') && isspace(*ptrs[i]); ptrs[i]++)
;
}
if (cols_done == ncols)
return 0;
if ((lp - line) > BUFFER_LEN)
lp = (line + BUFFER_LEN - 1);
*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 ansi_data adata[MAX_COLS];
static char *ptrs[MAX_COLS];
char *ansistr;
char filler;
char *fieldsep;
int fslen;
char *linesep;
int lslen;
int totallen = 0;
filler = ' ';
fieldsep = (char *) " ";
linesep = (char *) "\n";
memset(adata, 0, sizeof(adata));
/* Get column widths */
ncols = 0;
for (ptr = args[0]; *ptr; ptr++) {
while (isspace(*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 (*ptr == '_') {
calign[ncols] = AL_FULL;
ptr++;
} else if (*ptr == '=') {
calign[ncols] = AL_WPFULL;
ptr++;
} else if (isdigit(*ptr)) {
calign[ncols] = AL_LEFT;
} else {
safe_str(T("#-1 INVALID ALIGN STRING"), buff, bp);
return;
}
for (i = 0; *ptr && isdigit(*ptr); ptr++) {
i *= 10;
i += *ptr - '0';
}
while (*ptr && !isspace(*ptr)) {
switch (*ptr) {
case '.':
calign[ncols] |= AL_REPEAT;
break;
case '`':
calign[ncols] |= AL_COALESCE_LEFT;
break;
case '\'':
calign[ncols] |= AL_COALESCE_RIGHT;
break;
case '$':
calign[ncols] |= AL_NOFILL;
break;
case 'x':
calign[ncols] |= AL_TRUNC_EACH;
break;
case 'X':
calign[ncols] |= AL_TRUNC_ALL;
break;
case '#':
calign[ncols] |= AL_NOCOLSEP;
break;
case '(':
ptr++;
ansistr = ptr;
while (*ptr && *ptr != ')')
ptr++;
if (*ptr != ')') {
safe_str(T("#-1 INVALID ALIGN STRING"), buff, bp);
return;
}
*ptr = '\0';
define_ansi_data(&adata[ncols], ansistr);
break;
}
ptr++;
}
cols[ncols++] = i;
if (!*ptr)
break;
}
for (i = 0; i < ncols; i++) {
if (cols[i] < 0) {
safe_str(T("#-1 CANNOT HAVE COLUMNS OF NEGATIVE SIZE"), buff, bp);
return;
}
if (cols[i] > BUFFER_LEN) {
safe_str(T("#-1 CANNOT HAVE COLUMNS THAT LARGE"), buff, bp);
return;
}
if (0 && (calign[i] & AL_REPEAT))
calign[i] &=
~(AL_TRUNC_EACH | AL_TRUNC_ALL); /* Don't allow trunc + repeat */
totallen += cols[i];
}
if (totallen > BUFFER_LEN) {
safe_str(T("#-1 CANNOT HAVE COLUMNS THAT LARGE"), 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 (strcmp(called_as, "LALIGN")) {
/* each column is a separate arg */
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] || arglens[ncols + 1] > 1) {
safe_str(T("#-1 FILLER MUST BE ONE CHARACTER"), buff, bp);
return;
}
if (*args[ncols + 1]) {
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;
}
} else {
/* columns are in args[1] as an args[2]-separated list */
char delim, *s;
if (!delim_check(buff, bp, nargs, args, 3, &delim))
return;
if (do_wordcount(args[1], delim) != ncols) {
safe_str(T("#-1 INVALID NUMBER OF ARGUMENTS TO ALIGN"), buff, bp);
return;
}
if (nargs > 3) {
if (!args[3] || arglens[3] > 1) {
safe_str(T("#-1 FILLER MUST BE ONE CHARACTER"), buff, bp);
return;
}
if (*args[3])
filler = *(args[3]);
}
if (nargs > 4)
fieldsep = args[4];
if (nargs > 5)
linesep = args[5];
fslen = strlen(fieldsep);
lslen = strlen(linesep);
for (i = 0; i < MAX_COLS; i++) {
as[i] = NULL;
}
s = trim_space_sep(args[1], delim);
for (i = 0; i < ncols; i++) {
as[i] = parse_ansi_string(split_token(&s, delim));
ptrs[i] = as[i]->text;
}
}
nline = 0;
while (1) {
if (!align_one_line(buff, bp, ncols, cols, calign, ptrs, as, adata, 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;
}
FUNCTION(fun_speak)
{
ufun_attrib transufun;
ufun_attrib nullufun;
dbref speaker = NOTHING;
char *speaker_str;
const char *speaker_name;
char *open, *close;
char *start, *end = NULL;
bool transform = 0, null = 0, say = 0, starting_fragment = 0;
int funccount;
int fragment = 0;
char *say_string;
char *string;
char rbuff[BUFFER_LEN];
PE_REGS *pe_regs;
if (*args[0] == '&') {
speaker_str = args[0];
speaker_name = args[0] + 1;
} else {
speaker = match_thing(executor, args[0]);
if (speaker == NOTHING || speaker == AMBIGUOUS) {
safe_str(T(e_match), buff, bp);
return;
}
speaker_str = unparse_dbref(speaker);
speaker_name = accented_name(speaker);
}
if (!args[1] || !*args[1])
return;
string = args[1];
if (nargs > 2 && *args[2] != '\0' && *args[2] != ' ')
say_string = args[2];
else
say_string = (char *) "says,";
if (nargs > 3) {
if (args[3]) {
/* we have a transform attr */
transform = 1;
if (!fetch_ufun_attrib(args[3], executor, &transufun, UFUN_DEFAULT)) {
safe_str(T(e_atrperm), buff, bp);
return;
}
if (nargs > 4) {
if (args[4]) {
/* we have an attr to use when transform returns an empty string */
null = 1;
if (!fetch_ufun_attrib(args[4], executor, &nullufun, UFUN_DEFAULT)) {
safe_str(T(e_atrperm), buff, bp);
return;
}
}
}
}
}
if (nargs < 6 || !args[5])
open = (char *) "\"";
else
open = args[5];
if (nargs < 7 || !args[6])
close = open;
else
close = args[6];
switch (*string) {
case ':':
safe_str(speaker_name, buff, bp);
string++;
safe_chr(' ', buff, bp);
while (*string == ' ')
string++;
break;
case ';':
string++;
safe_str(speaker_name, buff, bp);
if (*string == ' ') {
/* pose it instead */
safe_chr(' ', buff, bp);
while (*string == ' ')
string++;
}
break;
case '|':
string++;
break;
case '"':
if (CHAT_STRIP_QUOTE)
string++;
/* FALL THROUGH */
default:
say = 1;
break;
}
start = strstr(string, open);
if (!transform || (!say && !start)) {
/* nice and easy */
if (say)
safe_format(buff, bp, "%s %s \"%s\"", speaker_name, say_string, string);
else
safe_str(string, buff, bp);
return;
}
/* If we're in say mode, prefix the buffer */
if (say) {
if (speaker != NOTHING)
safe_str(accented_name(speaker), buff, bp);
else
safe_str(speaker_name, buff, bp);
safe_chr(' ', buff, bp);
safe_str(say_string, buff, bp);
safe_chr(' ', buff, bp);
fragment = -1;
/* Special case: In say mode, if the delim isn't a quote, then
* we just pretend that we're otherwise in pose mode
*/
if (strcmp(open, "\"")) {
say = 0;
}
}
/* If the first char of the string is an open quote, copy it,
* skip it, and we're good to transform. Otherwise, if
* we're not in say mode, copy up to the first open quote
* and then transform. There must be an open quote somewhere
* if we're not in say mode.
*/
if (string_prefix(string, open)) {
if (say)
safe_str(open, buff, bp);
start += strlen(open);
} else if (!say && start) {
/* start points to first open quote, and after first char */
safe_str(chopstr(string, start - string + 1), buff, bp);
fragment = 0;
/* Don't add the quote in */
start += strlen(open);
} else {
/* We're in say mode and the first char isn't open, start there */
start = string;
starting_fragment = 1;
}
pe_regs = pe_regs_create(PE_REGS_ARG, "fun_speak");
while (start && *start) {
fragment++;
/* Transform to the next close, or to the end of the string. */
if ((end = strstr(start, close)) != NULL)
*end++ = '\0';
pe_regs_setenv_nocopy(pe_regs, 0, start);
pe_regs_setenv_nocopy(pe_regs, 1, speaker_str);
pe_regs_setenv(pe_regs, 2, unparse_integer(fragment));
if (call_ufun(&transufun, rbuff, executor, enactor, pe_info, pe_regs))
break;
pe_regs_clear(pe_regs);
funccount = pe_info->fun_invocations;
if (!*rbuff && (null == 1)) {
pe_regs_setenv_nocopy(pe_regs, 0, speaker_str);
pe_regs_setenv(pe_regs, 1, unparse_integer(fragment));
if (call_ufun(&nullufun, rbuff, executor, enactor, pe_info, pe_regs))
break;
pe_regs_clear(pe_regs);
}
if (*rbuff)
safe_str(rbuff, buff, bp);
if (((*bp - buff) >= BUFFER_LEN - 1) &&
(pe_info->fun_invocations == funccount))
break;
if (end && *end) {
if (!starting_fragment) {
if (say)
safe_str(close, buff, bp);
starting_fragment = 0;
}
}
if (!end || !*end)
break;
start = strstr(end, open);
if (start) {
/* Copy text not being transformed. */
if (start && *start && (start - end > (ptrdiff_t) strlen(open)))
safe_str(chopstr(end, start - end + 1), buff, bp);
start += strlen(open);
end = NULL;
} else {
/* No more opens, so we're done, and end has the rest */
break;
}
if (((*bp - buff) >= BUFFER_LEN - 1) &&
(pe_info->fun_invocations == funccount))
break;
}
if (end && *end)
safe_str(end, buff, bp);
pe_regs_free(pe_regs);
}
/* ARGSUSED */
FUNCTION(fun_render)
{
int flags = 0;
char *word, *list;
list = trim_space_sep(args[1], ' ');
do {
word = split_token(&list, ' ');
if (!word || !*word)
continue;
if (strcasecmp("ansi", word) == 0) {
if (Can_Nspemit(executor)) {
flags |= MSG_XTERM256;
} else {
safe_str(T(e_perm), buff, bp);
return;
}
} else if (string_prefix("noaccents", word))
flags |= MSG_STRIPACCENTS;
else if (strcasecmp("markup", word) == 0)
flags |= MSG_MARKUP;
else if (strcasecmp("html", word) == 0)
flags |= MSG_PUEBLO;
else {
safe_str(T("#-1 INVALID SECOND ARGUMENT"), buff, bp);
return;
}
} while (list);
if (!flags)
safe_str(remove_markup(args[0], NULL), buff, bp);
else
safe_str(render_string(args[0], flags), buff, bp);
}