char * term_minibuf_read (const char *prompt, const char *value, size_t pos, Completion * cp, History * hp) { Window *wp, *old_wp = cur_wp; char *s = NULL; astr as; if (hp) prepare_history (hp); as = do_minibuf_read (prompt, value, pos, cp, hp); if (as) { s = xstrdup (astr_cstr (as)); astr_delete (as); } if (cp != NULL && (get_completion_flags (cp) & CFLAG_POPPEDUP) && (wp = find_window ("*Completions*")) != NULL) { set_current_window (wp); if (get_completion_flags (cp) & CFLAG_CLOSE) FUNCALL (delete_window); else if (get_completion_old_bp (cp)) switch_to_buffer (get_completion_old_bp (cp)); set_current_window (old_wp); } return s; }
/* compile, link, and load the file */ static void process_file(struct astr *sfilename) { char *dsofile; struct dso_entry *entry; int r; fprintf(stderr, PROCTEXT("Compiling %s...\n"), astr_cstr(sfilename)); dsofile = compile(sfilename); if(dsofile == NULL) { fprintf(stderr, ERRORTEXT("Compilation failed.\n")); return; } fprintf(stderr, SUCCESSTEXT("Compilation succeeded.\n")); fprintf(stderr, PROCTEXT("Loading %s...\n"), dsofile); entry = load(dsofile); if(entry == NULL) { fprintf(stderr, ERRORTEXT("Load failed.\n")); r = unlink(dsofile); if(r != 0) { fprintf(stderr, ERRORTEXT("Failed to unlink %s") ": %s\n", dsofile, strerror(errno)); } return; } fprintf(stderr, SUCCESSTEXT("Load succeeded.\n")); run(entry); }
/* * Return a `~/foo' like path if the user is under his home directory, * and restart from / if // found, * else the unmodified path. */ astr compact_path(const astr path) { astr buf = astr_new(); struct passwd *pw; size_t i; if ((pw = getpwuid(getuid())) == NULL) { /* User not found in password file. */ astr_cpy(buf, path); return buf; } /* Replace `/userhome/' (if existent) with `~/'. */ i = strlen(pw->pw_dir); if (!strncmp(pw->pw_dir, astr_cstr(path), i)) { astr_cpy_cstr(buf, "~/"); if (!strcmp(pw->pw_dir, "/")) astr_cat_cstr(buf, astr_char(path, 1)); else astr_cat_cstr(buf, astr_char(path, i + 1)); } else astr_cpy(buf, path); return buf; }
static int find_substr (astr as, const char *s2, size_t s2size, size_t from, size_t to, bool forward, bool notbol, bool noteol, bool regex, bool icase) { int ret = -1; struct re_pattern_buffer pattern; struct re_registers search_regs; reg_syntax_t syntax = RE_SYNTAX_EMACS; memset (&pattern, 0, sizeof (pattern)); if (!regex) syntax |= RE_PLAIN; if (icase) syntax |= RE_ICASE; re_set_syntax (syntax); search_regs.num_regs = 1; re_find_err = re_compile_pattern (s2, (int) s2size, &pattern); pattern.not_bol = notbol; pattern.not_eol = noteol; if (!re_find_err) ret = re_search (&pattern, astr_cstr (as), (int) astr_len (as), forward ? from : to - 1, forward ? (to - from) : -(to - 1 - from), &search_regs); if (ret >= 0) { ret = forward ? search_regs.end[0] : ret; free (search_regs.start); free (search_regs.end); } regfree (&pattern); return ret; }
static void draw_status_line(size_t line, Window *wp) { size_t i; char *buf, *eol_type; Point pt = window_pt(wp); astr as, bs; term_attrset(1, FONT_REVERSE); term_move(line, 0); for (i = 0; i < wp->ewidth; ++i) term_addch('-'); if (cur_bp->eol == coding_eol_cr) eol_type = "(Mac)"; else if (cur_bp->eol == coding_eol_crlf) eol_type = "(DOS)"; else eol_type = ":"; term_move(line, 0); bs = astr_afmt(astr_new(), "(%d,%d)", pt.n+1, get_goalc_wp(wp)); as = astr_afmt(astr_new(), "--%s%2s %-15s %s %-9s (Text", eol_type, make_mode_line_flags(wp), wp->bp->name, make_screen_pos(wp, &buf), astr_cstr(bs)); free(buf); astr_delete(bs); if (wp->bp->flags & BFLAG_AUTOFILL) astr_cat_cstr(as, " Fill"); if (wp->bp->flags & BFLAG_OVERWRITE) astr_cat_cstr(as, " Ovwrt"); if (thisflag & FLAG_DEFINING_MACRO) astr_cat_cstr(as, " Def"); if (wp->bp->flags & BFLAG_ISEARCH) astr_cat_cstr(as, " Isearch"); astr_cat_char(as, ')'); term_addnstr(astr_cstr(as), min(term_width(), astr_len(as))); astr_delete(as); term_attrset(1, FONT_NORMAL); }
/* * Return a `~/foo' like path if the user is under his home directory, * else the unmodified path. */ astr compact_path (astr path) { struct passwd *pw = getpwuid (getuid ()); if (pw != NULL) { /* Replace `/userhome/' (if found) with `~/'. */ size_t homelen = strlen (pw->pw_dir); if (homelen > 0 && pw->pw_dir[homelen - 1] == '/') homelen--; if (astr_len (path) > homelen && !strncmp (pw->pw_dir, astr_cstr (path), homelen) && astr_get (path, homelen) == '/') astr_cpy (path, astr_cat_cstr (astr_new_cstr ("~/"), astr_cstr (path) + homelen + 1)); } return path; }
static void draw_status_line (size_t line, Window wp) { term_attrset (FONT_REVERSE); term_move (line, 0); for (size_t i = 0; i < get_window_ewidth (wp); ++i) term_addstr ("-"); const char *eol_type; if (get_buffer_eol (cur_bp) == coding_eol_cr) eol_type = "(Mac)"; else if (get_buffer_eol (cur_bp) == coding_eol_crlf) eol_type = "(DOS)"; else eol_type = ":"; term_move (line, 0); size_t n = offset_to_line (get_window_bp (wp), window_o (wp)); astr as = astr_fmt ("--%s%2s %-15s %s %-9s (Fundamental", eol_type, make_mode_line_flags (wp), get_buffer_name (get_window_bp (wp)), make_screen_pos (wp), astr_cstr (astr_fmt ("(%zu,%zu)", n + 1, get_goalc_bp (get_window_bp (wp), window_o (wp))))); if (get_buffer_autofill (get_window_bp (wp))) astr_cat_cstr (as, " Fill"); if (thisflag & FLAG_DEFINING_MACRO) astr_cat_cstr (as, " Def"); if (get_buffer_isearch (get_window_bp (wp))) astr_cat_cstr (as, " Isearch"); astr_cat_char (as, ')'); term_addstr (astr_cstr (as)); term_attrset (FONT_NORMAL); }
/* * This function calculates the best start column to draw if the line * needs to get truncated. * Called only for the line where is the point. */ static void calculate_start_column(Window *wp) { size_t col = 0, lastcol = 0, t = tab_width(wp->bp); int rpfact, lpfact; char *buf, *rp, *lp, *p; Point pt = window_pt(wp); rp = astr_char(pt.p->item, (ptrdiff_t)pt.o); rpfact = pt.o / (wp->ewidth / 3); for (lp = rp; lp >= astr_cstr(pt.p->item); --lp) { for (col = 0, p = lp; p < rp; ++p) if (*p == '\t') { col |= t - 1; ++col; } else if (isprint(*p)) ++col; else { col += make_char_printable(&buf, (size_t)*p); free(buf); } lpfact = (lp - astr_cstr(pt.p->item)) / (wp->ewidth / 3); if (col >= wp->ewidth - 1 || lpfact < (rpfact - 2)) { wp->start_column = lp + 1 - astr_cstr(pt.p->item); point_screen_column = lastcol; return; } lastcol = col; } wp->start_column = 0; point_screen_column = col; }
/* * Revert an action. Return the next undo entry. */ static Undo * revert_action (Undo * up) { size_t i; Point pt; pt.n = up->n; pt.o = up->o; doing_undo = true; if (up->type == UNDO_END_SEQUENCE) { undo_save (UNDO_START_SEQUENCE, pt, 0, 0); up = up->next; while (up->type != UNDO_START_SEQUENCE) up = revert_action (up); pt.n = up->n; pt.o = up->o; undo_save (UNDO_END_SEQUENCE, pt, 0, 0); goto_point (pt); return up->next; } goto_point (pt); if (up->type == UNDO_REPLACE_BLOCK) { undo_save (UNDO_REPLACE_BLOCK, pt, up->block.size, up->block.osize); undo_nosave = true; for (i = 0; i < up->block.size; ++i) delete_char (); insert_nstring (astr_cstr (up->block.text), up->block.osize); undo_nosave = false; } doing_undo = false; if (up->unchanged) set_buffer_modified (cur_bp, false); return up->next; }
struct le *parseInFile(getcCallback getachar, ungetcCallback ungetachar, struct le *list, int *line) { astr tok; enum tokenname tokenid; int isquoted = 0; assert(getachar && ungetachar); while (1) { tok = snagAToken(getachar, ungetachar, &tokenid); switch (tokenid) { case T_QUOTE: isquoted = 1; break; case T_OPENPAREN: list = leAddBranchElement(list, parseInFile(getachar, ungetachar, NULL, line), isquoted); isquoted = 0; break; case T_NEWLINE: isquoted = 0; *line = *line +1; break; case T_WORD: list = leAddDataElement(list, astr_cstr(tok), isquoted); isquoted = 0; break; case T_CLOSEPAREN: case T_EOF: isquoted = 0; astr_delete(tok); return list; } astr_delete(tok); } }
/* set up the appropriate watch on the directory indicated by sfilename */ static int setup_inotify_watch(int notify_fd, struct astr *sfilename) { char dirbuf[astr_len(sfilename) + 1]; char *dir; int dwatch; strcpy(dirbuf, astr_cstr(sfilename)); dir = dirname(dirbuf); dwatch = inotify_add_watch(notify_fd, dir, IN_CLOSE_WRITE|IN_MOVED_TO); if(dwatch <= 0) { fprintf(stderr, ERRORTEXT("Fatal: failed to add inotify watch for %s") ": %s\n", dir, strerror(errno)); exit(EXIT_FAILURE); } return dwatch; }
/* * This functions does some corrections and expansions to * the passed path: * * - expands `~/' and `~name/' expressions; * - replaces `//' with `/' (restarting from the root directory); * - removes `..' and `.' entries. * * The return value indicates success or failure. */ bool expand_path (astr path) { int ok = true; const char *sp = astr_cstr (path); astr epath = astr_new (); if (*sp != '/' && *sp != '~') { astr_cat (epath, agetcwd ()); if (astr_len (epath) == 0 || astr_get (epath, astr_len (epath) - 1) != '/') astr_cat_char (epath, '/'); } for (const char *p = sp; *p != '\0';) { if (*p == '/') { if (*++p == '/') { /* Got `//'. Restart from this point. */ while (*p == '/') p++; astr_truncate (epath, 0); } if (astr_len (epath) == 0 || astr_get (epath, astr_len (epath) - 1) != '/') astr_cat_char (epath, '/'); } else if (*p == '~' && (p == sp || p[-1] == '/')) { /* Got `/~' or leading `~'. Restart from this point. */ struct passwd *pw; astr_truncate (epath, 0); ++p; if (*p == '/') { /* Got `~/'. Insert the user's home directory. */ pw = getpwuid (getuid ()); if (pw == NULL) { ok = false; break; } if (!STREQ (pw->pw_dir, "/")) astr_cat_cstr (epath, pw->pw_dir); } else { /* Got `~something'. Insert that user's home directory. */ astr as = astr_new (); while (*p != '\0' && *p != '/') astr_cat_char (as, *p++); pw = getpwnam (astr_cstr (as)); if (pw == NULL) { ok = false; break; } astr_cat_cstr (epath, pw->pw_dir); } } else if (*p == '.' && (p[1] == '/' || p[1] == '\0')) { /* Got `.'. */ ++p; } else if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { /* Got `..'. */ if (astr_len (epath) >= 1 && astr_get (epath, astr_len (epath) - 1) == '/') astr_truncate (epath, astr_len (epath) - 1); while (astr_get (epath, astr_len (epath) - 1) != '/' && astr_len (epath) >= 1) astr_truncate (epath, astr_len (epath) - 1); p += 2; } if (*p != '~') while (*p != '\0' && *p != '/') astr_cat_char (epath, *p++); } astr_cpy (path, epath); return ok; }
return true; } DEFUN_ARGS ("find-file", find_file, STR_ARG (filename)) /*+ Edit file @i{filename}. Switch to a buffer visiting file @i{filename}, creating one if none already exists. +*/ { STR_INIT (filename) else { filename = minibuf_read_filename ("Find file: ", astr_cstr (get_buffer_dir (cur_bp)), NULL); if (filename == NULL) ok = FUNCALL (keyboard_quit); } if (filename == NULL || astr_len (filename) == 0) ok = leNIL; if (ok != leNIL) ok = bool_to_lisp (find_file (astr_cstr (filename))); } END_DEFUN DEFUN ("find-file-read-only", find_file_read_only) /*+
/* * This functions does some corrections and expansions to * the passed path: * - expands `~/' and `~name/' expressions; * - replaces `//' with `/' (restarting from the root directory); * - removes `..' and `.' entries. * * If something goes wrong, the string is deleted and NULL returned */ astr expand_path(astr path) { int ret = TRUE; struct passwd *pw; const char *sp = astr_cstr(path); astr epath = astr_new(); if (*sp != '/') { astr_cat_delete(epath, agetcwd()); if (astr_len(epath) == 0 || *astr_char(epath, -1) != '/') astr_cat_char(epath, '/'); } while (*sp != '\0') { if (*sp == '/') { if (*++sp == '/') { /* Got `//'. Restart from this point. */ while (*sp == '/') sp++; astr_truncate(epath, 0); } astr_cat_char(epath, '/'); } else if (*sp == '~') { if (*(sp + 1) == '/') { /* Got `~/'. Restart from this point and insert the user's home directory. */ astr_truncate(epath, 0); if ((pw = getpwuid(getuid())) == NULL) { ret = FALSE; break; } if (strcmp(pw->pw_dir, "/") != 0) astr_cat_cstr(epath, pw->pw_dir); ++sp; } else { /* Got `~something'. Restart from this point and insert that user's home directory. */ astr as = astr_new(); astr_truncate(epath, 0); ++sp; while (*sp != '\0' && *sp != '/') astr_cat_char(as, *sp++); pw = getpwnam(astr_cstr(as)); astr_delete(as); if (pw == NULL) { ret = FALSE; break; } astr_cat_cstr(epath, pw->pw_dir); } } else if (*sp == '.') { if (*(sp + 1) == '/' || *(sp + 1) == '\0') { ++sp; if (*sp == '/' && *(sp + 1) != '/') ++sp; } else if (*(sp + 1) == '.' && (*(sp + 2) == '/' || *(sp + 2) == '\0')) { if (astr_len(epath) >= 1 && *astr_char(epath, -1) == '/') astr_truncate(epath, -1); while (*astr_char(epath, -1) != '/' && astr_len(epath) >= 1) astr_truncate(epath, -1); sp += 2; if (*sp == '/' && *(sp + 1) != '/') ++sp; } else goto got_component; } else { const char *p; got_component: p = sp; while (*p != '\0' && *p != '/') p++; if (*p == '\0') { /* Final filename */ astr_cat_cstr(epath, sp); break; } else { /* Non-final directory */ while (*sp != '/') astr_cat_char(epath, *sp++); } } } astr_cpy(path, epath); astr_delete(epath); if (!ret) { astr_delete(path); return NULL; } return path; }
static astr do_minibuf_read (const char *prompt, const char *value, size_t pos, Completion * cp, History * hp) { static int overwrite_mode = 0; int c, thistab, lasttab = -1; size_t prompt_len; char *s; astr as = astr_new_cstr (value), saved = NULL; prompt_len = strlen (prompt); if (pos == SIZE_MAX) pos = astr_len (as); for (;;) { switch (lasttab) { case COMPLETION_MATCHEDNONUNIQUE: s = " [Complete, but not unique]"; break; case COMPLETION_NOTMATCHED: s = " [No match]"; break; case COMPLETION_MATCHED: s = " [Sole completion]"; break; default: s = ""; } draw_minibuf_read (prompt, astr_cstr (as), prompt_len, s, pos); thistab = -1; switch (c = getkey ()) { case KBD_NOKEY: break; case KBD_CTRL | 'z': FUNCALL (suspend_emacs); break; case KBD_RET: term_move (term_height () - 1, 0); term_clrtoeol (); if (saved) astr_delete (saved); return as; case KBD_CANCEL: term_move (term_height () - 1, 0); term_clrtoeol (); if (saved) astr_delete (saved); astr_delete (as); return NULL; case KBD_CTRL | 'a': case KBD_HOME: pos = 0; break; case KBD_CTRL | 'e': case KBD_END: pos = astr_len (as); break; case KBD_CTRL | 'b': case KBD_LEFT: if (pos > 0) --pos; else ding (); break; case KBD_CTRL | 'f': case KBD_RIGHT: if (pos < astr_len (as)) ++pos; else ding (); break; case KBD_CTRL | 'k': /* FIXME: do kill-register save. */ if (pos < astr_len (as)) astr_truncate (as, pos); else ding (); break; case KBD_BS: if (pos > 0) astr_remove (as, --pos, 1); else ding (); break; case KBD_CTRL | 'd': case KBD_DEL: if (pos < astr_len (as)) astr_remove (as, pos, 1); else ding (); break; case KBD_INS: overwrite_mode = overwrite_mode ? 0 : 1; break; case KBD_META | 'v': case KBD_PGUP: if (cp == NULL) { ding (); break; } if (get_completion_flags (cp) & CFLAG_POPPEDUP) { completion_scroll_down (); thistab = lasttab; } break; case KBD_CTRL | 'v': case KBD_PGDN: if (cp == NULL) { ding (); break; } if (get_completion_flags (cp) & CFLAG_POPPEDUP) { completion_scroll_up (); thistab = lasttab; } break; case KBD_UP: case KBD_META | 'p': if (hp) { const char *elem = previous_history_element (hp); if (elem) { if (!saved) saved = astr_cpy (astr_new (), as); astr_cpy_cstr (as, elem); } } break; case KBD_DOWN: case KBD_META | 'n': if (hp) { const char *elem = next_history_element (hp); if (elem) astr_cpy_cstr (as, elem); else if (saved) { astr_cpy (as, saved); astr_delete (saved); saved = NULL; } } break; case KBD_TAB: got_tab: if (cp == NULL) { ding (); break; } if (lasttab != -1 && lasttab != COMPLETION_NOTMATCHED && get_completion_flags (cp) & CFLAG_POPPEDUP) { completion_scroll_up (); thistab = lasttab; } else { astr bs = astr_new (); astr_cpy (bs, as); thistab = completion_try (cp, bs, true); astr_delete (bs); switch (thistab) { case COMPLETION_MATCHED: case COMPLETION_MATCHEDNONUNIQUE: case COMPLETION_NONUNIQUE: { bs = astr_new (); if (get_completion_flags (cp) & CFLAG_FILENAME) astr_cat (bs, get_completion_path (cp)); astr_ncat_cstr (bs, get_completion_match (cp), get_completion_matchsize (cp)); if (strncmp (astr_cstr (as), astr_cstr (bs), astr_len (bs)) != 0) thistab = -1; astr_delete (as); as = bs; pos = astr_len (as); break; } case COMPLETION_NOTMATCHED: ding (); } } break; case ' ': if (cp != NULL) goto got_tab; /* FALLTHROUGH */ default: if (c > 255 || !isprint (c)) { ding (); break; } astr_insert_char (as, pos++, c); if (overwrite_mode && pos != astr_len (as)) astr_remove (as, pos, 1); } lasttab = thistab; } }
void watch_file() { int r; int notify_fd; int dwatch; struct astr *sfilename; char *file; uint8_t inotify_buf[sizeof(struct inotify_event) + NAME_MAX + 1]; struct inotify_event *event; size_t i; ssize_t len; /* initialize the inotify system */ notify_fd = inotify_init(); if(notify_fd < 0) { perror(ERRORTEXT("Fatal: failed to initialize " "inotify system")); exit(EXIT_FAILURE); } setup_watch: /* set up the inotify watch and associated variables */ sfilename = (struct astr *) arcp_load(&livec_opts.filename); if(sfilename == NULL) { fprintf(stderr, ERRORTEXT("Fatal: no filename defined\n")); exit(EXIT_FAILURE); } file = simple_basename(astr_cstr(sfilename)); dwatch = setup_inotify_watch(notify_fd, sfilename); /* process the file */ process_file(sfilename); /* main watch loop */ for(;;) { if(sfilename != (struct astr *) arcp_load_phantom(&livec_opts.filename)) { /* the filename option has changed; remove the watch * and restart */ arcp_release(sfilename); r = inotify_rm_watch(notify_fd, dwatch); if(r != 0) { perror(ERRORTEXT("Failed to clean" " up old watch")); } goto setup_watch; } /* block until there's at least one event to be notified * about */ len = read(notify_fd, inotify_buf, sizeof(struct inotify_event) + NAME_MAX + 1); if(len <= 0) { perror(ERRORTEXT("read() of inotify event failed")); continue; } /* read through all events */ for(i = 0; i <= len - sizeof(struct inotify_event);) { event = (struct inotify_event *) &inotify_buf[i]; i += sizeof(struct inotify_event) + event->len; if(event->wd != dwatch) { /* FIXME: why wouldn't this be the same? */ continue; } if((event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO)) && (strcmp(event->name, file) == 0)) { /* FIXME: do we need the event mask test * here? */ /* the (directory) event was about the file * we're interested in */ process_file(sfilename); break; } } } }
int main(void) { astr as1, as2, as3; int i; as1 = astr_new(); astr_assign_cstr(as1, " world"); astr_prepend_cstr(as1, "hello"); astr_append_char(as1, '!'); assert_eq(as1, "hello world!"); as3 = astr_substr(as1, 6, 5); assert_eq(as3, "world"); as2 = astr_new(); astr_assign_cstr(as2, " "); astr_prepend_cstr(as2, "The"); astr_append(as2, as3); astr_append_char(as2, '.'); assert_eq(as2, "The world."); astr_delete(as3); as3 = astr_substr(as1, -6, 5); assert_eq(as3, "world"); astr_assign_cstr(as1, "12345"); astr_delete(as2); as2 = astr_left(as1, 10); assert_eq(as2, "12345"); astr_delete(as2); as2 = astr_left(as1, 3); assert_eq(as2, "123"); astr_delete(as2); as2 = astr_right(as1, 10); assert_eq(as2, "12345"); astr_delete(as2); as2 = astr_right(as1, 3); assert_eq(as2, "345"); astr_assign_cstr(as1, "12345"); astr_insert_cstr(as1, 3, "mid"); astr_insert_cstr(as1, 0, "begin"); astr_insert_cstr(as1, 100, "end"); assert_eq(as1, "begin123mid45end"); astr_assign_cstr(as1, "12345"); astr_insert_char(as1, -2, 'x'); astr_insert_char(as1, -10, 'y'); astr_insert_char(as1, 10, 'z'); assert_eq(as1, "y123x45z"); astr_assign_cstr(as1, "12345"); astr_delete(as2); as2 = astr_substr(as1, -2, 5); assert_eq(as2, "45"); astr_assign_cstr(as1, "12345"); astr_delete(as2); as2 = astr_substr(as1, -10, 5); assert_eq(as2, "12345"); astr_assign_cstr(as1, "1234567"); astr_replace_cstr(as1, -4, 2, "foo"); assert_eq(as1, "123foo67"); astr_assign_cstr(as1, "1234567"); astr_replace_cstr(as1, 1, 3, "foo"); assert_eq(as1, "1foo567"); astr_assign_cstr(as1, "1234567"); astr_replace_cstr(as1, -1, 5, "foo"); assert_eq(as1, "123456foo"); astr_assign_cstr(as1, "1234567"); astr_remove(as1, 4, 10); assert_eq(as1, "1234"); astr_assign_cstr(as1, "abc def de ab cd ab de fg"); while ((i = astr_find_cstr(as1, "de")) >= 0) astr_replace_cstr(as1, i, 2, "xxx"); assert_eq(as1, "abc xxxf xxx ab cd ab xxx fg"); while ((i = astr_find_cstr(as1, "ab")) >= 0) astr_remove(as1, i, 2); assert_eq(as1, "c xxxf xxx cd xxx fg"); while ((i = astr_find_cstr(as1, " ")) >= 0) astr_replace_char(as1, i, 2, ' '); assert_eq(as1, "c xxxf xxx cd xxx fg"); astr_fill(as1, 'x', 3); assert_eq(as1, "xxx"); astr_fmt(as1, "%s * %d = ", "5", 3); astr_afmt(as1, "%d", 15); assert_eq(as1, "5 * 3 = 15"); printf("Input one string: "); fflush(stdout); astr_fgets(as1, stdin); printf("You wrote: \"%s\"\n", astr_cstr(as1)); astr_delete(as1); astr_delete(as2); astr_delete(as3); printf("astr test successful.\n"); return 0; }