Make fixes and improvements

- Fix handling of precision in hex float formatting
- Enhance the cocmd interpreter for system() and popen()
- Manually ran the Lua unit tests, which are now passing
- Let stdio i/o operations happen when file is in error state
- We're now saving and restoring xmm in ftrace out of paranoia
This commit is contained in:
Justine Tunney 2023-07-09 05:11:25 -07:00
parent 95fbdb4f76
commit 41396ff48a
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
43 changed files with 495 additions and 261 deletions

View file

@ -18,7 +18,6 @@ extern char *optarg;
extern int optind, opterr, optopt, optreset;
int getopt(int, char *const[], const char *) paramsnonnull();
int getsubopt(char **, char *const *, char **) paramsnonnull();
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -29,7 +29,6 @@
SUCH DAMAGE.
*/
#include "libc/str/str.h"
#include "third_party/getopt/getopt.internal.h"
// clang-format off
/*

View file

@ -26,6 +26,7 @@
*/
#define lua_c
#include "third_party/lua/lrepl.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/errno.h"
@ -44,7 +45,6 @@
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lprefix.h"
#include "third_party/lua/lrepl.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/lualib.h"
// clang-format off
@ -70,8 +70,8 @@ bool lua_repl_blocking;
bool lua_repl_isterminal;
linenoiseCompletionCallback *lua_repl_completions_callback;
struct linenoiseState *lua_repl_linenoise;
const char *lua_progname;
static lua_State *globalL;
static const char *g_progname;
static const char *g_historypath;
/*
@ -253,6 +253,7 @@ static ssize_t pushline (lua_State *L, int firstline) {
} else {
lua_repl_unlock();
fputs(prmt, stdout);
free(prmt);
fflush(stdout);
b = linenoiseGetLine(stdin);
if (b) {
@ -328,16 +329,15 @@ static int multiline (lua_State *L) {
}
void lua_initrepl(lua_State *L, const char *progname) {
void lua_initrepl(lua_State *L) {
const char *prompt;
lua_repl_lock();
g_progname = progname;
if ((lua_repl_isterminal = linenoiseIsTerminal())) {
linenoiseSetCompletionCallback(lua_readline_completions);
linenoiseSetHintsCallback(lua_readline_hint);
linenoiseSetFreeHintsCallback(free);
prompt = get_prompt(L, 1);
if ((g_historypath = linenoiseGetHistoryPath(progname))) {
if ((g_historypath = linenoiseGetHistoryPath(lua_progname))) {
if (linenoiseHistoryLoad(g_historypath) == -1) {
fprintf(stderr, "%r%s: failed to load history: %m%n", g_historypath);
free(g_historypath);
@ -469,7 +469,7 @@ void lua_l_print (lua_State *L) {
lua_getglobal(L, "print");
lua_insert(L, 1);
if (lua_pcall(L, n, 0, 0) != LUA_OK)
lua_l_message(g_progname, lua_pushfstring(L, "error calling 'print' (%s)",
lua_l_message(lua_progname, lua_pushfstring(L, "error calling 'print' (%s)",
lua_tostring(L, -1)));
}
}
@ -483,7 +483,7 @@ void lua_l_print (lua_State *L) {
int lua_report (lua_State *L, int status) {
if (status != LUA_OK) {
const char *msg = lua_tostring(L, -1);
lua_l_message(g_progname, msg);
lua_l_message(lua_progname, msg);
lua_pop(L, 1); /* remove message */
}
return status;

View file

@ -8,6 +8,7 @@ COSMOPOLITAN_C_START_
extern bool lua_repl_blocking;
extern bool lua_repl_isterminal;
extern const char *lua_progname;
extern struct linenoiseState *lua_repl_linenoise;
extern linenoiseCompletionCallback *lua_repl_completions_callback;
@ -16,8 +17,8 @@ void lua_repl_lock(void);
void lua_repl_unlock(void);
int lua_loadline(lua_State *);
void lua_l_print(lua_State *);
void lua_initrepl(lua_State *);
void lua_sigint(lua_State *, int);
void lua_initrepl(lua_State *, const char *);
int lua_report(lua_State *, int);
int lua_runchunk(lua_State *, int, int);
void lua_l_message(const char *, const char *);

View file

@ -57,7 +57,7 @@ Lua 5.4.3 (MIT License)\\n\
Copyright 19942021 Lua.org, PUC-Rio.\"");
asm(".include \"libc/disclaimer.inc\"");
STATIC_STACK_SIZE(0x40000);
STATIC_STACK_SIZE(0x80000);
#if !defined(LUA_PROGNAME)
#define LUA_PROGNAME "lua"
@ -71,7 +71,6 @@ STATIC_STACK_SIZE(0x40000);
static lua_State *globalL = NULL;
static const char *progname = LUA_PROGNAME;
static bool lua_stdin_is_tty(void) {
@ -80,7 +79,7 @@ static bool lua_stdin_is_tty(void) {
static void print_usage (const char *badoption) {
lua_writestringerror("%s: ", progname);
lua_writestringerror("%s: ", lua_progname);
if (badoption[1] == 'e' || badoption[1] == 'l')
lua_writestringerror("'%s' needs argument\n", badoption);
else
@ -97,7 +96,7 @@ static void print_usage (const char *badoption) {
" -- stop handling options\n"
" - stop handling options and execute stdin\n"
,
progname);
lua_progname);
}
@ -304,9 +303,9 @@ static int handle_luainit (lua_State *L) {
*/
static void doREPL (lua_State *L) {
int status;
const char *oldprogname = progname;
progname = NULL; /* no 'progname' on errors in interactive mode */
lua_initrepl(L, LUA_PROGNAME);
const char *oldprogname = lua_progname;
lua_progname = NULL; /* no 'progname' on errors in interactive mode */
lua_initrepl(L);
for (;;) {
if (lua_repl_isterminal)
linenoiseEnableRawMode(0);
@ -325,7 +324,7 @@ static void doREPL (lua_State *L) {
lua_pushfstring(L, "read error: %s", strerror(errno));
lua_report(L, status);
lua_freerepl();
progname = oldprogname;
lua_progname = oldprogname;
return;
}
if (status == LUA_OK)
@ -338,7 +337,7 @@ static void doREPL (lua_State *L) {
}
lua_freerepl();
lua_settop(L, 0); /* clear stack */
progname = oldprogname;
lua_progname = oldprogname;
}
/* }================================================================== */
@ -354,7 +353,8 @@ static int pmain (lua_State *L) {
int script;
int args = collectargs(argv, &script);
luaL_checkversion(L); /* check that interpreter has correct version */
if (argv[0] && argv[0][0]) progname = argv[0];
lua_progname = LUA_PROGNAME;
if (argv[0] && argv[0][0]) lua_progname = argv[0];
if (args == has_error) { /* bad arg? */
print_usage(argv[script]); /* 'script' has index of bad arg. */
return 0;

View file

@ -792,9 +792,18 @@ assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
local function checkDateTable (t)
_G.D = os.date("*t", t)
assert(os.time(D) == t)
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
D.hour==%H and D.min==%M and D.sec==%S and
D.wday==%w+1 and D.yday==%j)]], t))()
-- [jart] rewrote test due to octal
assert(string.format('%d', D.year) == os.date('%Y', t))
assert(string.format('%02d', D.month) == os.date('%m', t))
assert(string.format('%02d', D.day) == os.date('%d', t))
assert(string.format('%02d', D.hour) == os.date('%H', t))
assert(string.format('%02d', D.min) == os.date('%M', t))
assert(string.format('%02d', D.sec) == os.date('%S', t))
assert(string.format('%d', D.wday - 1) == os.date('%w', t))
assert(string.format('%03d', D.yday) == os.date('%j', t))
-- load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
-- D.hour==%H and D.min==%M and D.sec==%S and
-- D.wday==%w+1 and D.yday==%j)]], t))()
_G.D = nil
end

View file

@ -25,7 +25,7 @@ assert("\099" == '\99')
assert("\099\n" == 'c\10')
assert('\0\0\0alo' == '\0' .. '\0\0' .. 'alo')
assert(010 .. 020 .. -030 == "1020-30")
assert(10 .. 20 .. -30 == "1020-30") -- [jart] octal extension
-- hexadecimal escapes
assert("\x00\x05\x10\x1f\x3C\xfF\xe8" == "\0\5\16\31\60\255\232")

View file

@ -401,7 +401,7 @@ assert(tonumber("-0x"..string.rep("f", (intbits//4))) == 1)
-- testing 'tonumber' with base
assert(tonumber(' 001010 ', 2) == 10)
assert(tonumber(' 001010 ', 10) == 001010)
assert(tonumber(' 001010 ', 10) == 1010) -- [jart] octal extension fix
assert(tonumber(' -1010 ', 2) == -10)
assert(tonumber('10', 36) == 36)
assert(tonumber(' -10 ', 36) == -36)

View file

@ -1,53 +0,0 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi
Musl Libc
Copyright © 2005-2014 Rich Felker, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "third_party/musl/ftw.h"
asm(".ident\t\"\\n\\n\
Musl libc (MIT License)\\n\
Copyright 2005-2014 Rich Felker, et. al.\"");
asm(".include \"libc/disclaimer.inc\"");
// clang-format off
/**
* Walks file tree.
*
* @return 0 on success, -1 on error, or non-zero `fn` result
* @see examples/walk.c for example
* @see nftw()
*/
int ftw(const char *dirpath,
int fn(const char *fpath,
const struct stat *st,
int typeflag),
int fd_limit)
{
/* The following cast assumes that calling a function with one
* argument more than it needs behaves as expected. This is
* actually undefined, but works on all real-world machines. */
return nftw(dirpath, (int (*)())fn, fd_limit, FTW_PHYS);
}

View file

@ -1,90 +0,0 @@
#ifndef COSMOPOLITAN_THIRD_PARTY_MUSL_FTW_H_
#define COSMOPOLITAN_THIRD_PARTY_MUSL_FTW_H_
#include "libc/calls/struct/stat.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/**
* Type for file.
*/
#define FTW_F 1
/**
* Type for directory.
*/
#define FTW_D 2
/**
* Type for directory that cannot be read.
*/
#define FTW_DNR 3
/**
* Type for stat() failed and not a symbolic link.
*/
#define FTW_NS 4
/**
* Type for symbolic link when `FTW_PHYS` is in flags.
*/
#define FTW_SL 5
/**
* Directory and `FTW_DEPTH` in flags.
*/
#define FTW_DP 6
/**
* Type for broken symbolic link when `FTW_PHYS` is not in flags.
*/
#define FTW_SLN 7
/**
* Flag to prevent following symbolic links (recommended).
* @see nftw() flags
*/
#define FTW_PHYS 1
/**
* Flag to prevent crossing mount points.
* @see nftw() flags
*/
#define FTW_MOUNT 2
/**
* Unsupported.
* @see nftw() flags
*/
#define FTW_CHDIR 4
/**
* Flag for post-order traversal.
*
* 1. Will use `FTW_DP` instead of `FTW_D` as type.
* 2. Directory callback happens *after* rather than before.
*
* @see nftw() flags
*/
#define FTW_DEPTH 8
struct FTW {
/**
* Byte offset of basename component in `fpath` passed to callback.
*/
int base;
/**
* Depth relative to `dirpath` whose level is zero.
*/
int level;
};
int ftw(const char *, int (*)(const char *, const struct stat *, int), int);
int nftw(const char *,
int (*)(const char *, const struct stat *, int, struct FTW *), int,
int);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_THIRD_PARTY_MUSL_FTW_H_ */

View file

@ -1,189 +0,0 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi
Musl Libc
Copyright © 2005-2014 Rich Felker, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/weirdtypes.h"
#include "libc/errno.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/s.h"
#include "libc/thread/thread.h"
#include "third_party/musl/ftw.h"
#define PATH_MAXIMUS 4096
asm(".ident\t\"\\n\\n\
Musl libc (MIT License)\\n\
Copyright 2005-2014 Rich Felker, et. al.\"");
asm(".include \"libc/disclaimer.inc\"");
// clang-format off
struct history
{
struct history *chain;
dev_t dev;
ino_t ino;
int level;
int base;
};
static int do_nftw(char *path,
int fn(const char *, const struct stat *, int, struct FTW *),
int fd_limit,
int flags,
struct history *h)
{
size_t l = strlen(path), j = l && path[l-1]=='/' ? l-1 : l;
struct stat st;
struct history new;
int type;
int r;
int dfd=-1;
int err=0;
struct FTW lev;
if ((flags & FTW_PHYS) ? lstat(path, &st) : stat(path, &st) < 0) {
if (!(flags & FTW_PHYS) && errno==ENOENT && !lstat(path, &st))
type = FTW_SLN;
else if (errno != EACCES) return -1;
else type = FTW_NS;
} else if (S_ISDIR(st.st_mode)) {
if (flags & FTW_DEPTH) type = FTW_DP;
else type = FTW_D;
} else if (S_ISLNK(st.st_mode)) {
if (flags & FTW_PHYS) type = FTW_SL;
else type = FTW_SLN;
} else {
type = FTW_F;
}
if ((flags & FTW_MOUNT) && h && st.st_dev != h->dev)
return 0;
new.chain = h;
new.dev = st.st_dev;
new.ino = st.st_ino;
new.level = h ? h->level+1 : 0;
new.base = j+1;
lev.level = new.level;
if (h) {
lev.base = h->base;
} else {
size_t k;
for (k=j; k && path[k]=='/'; k--);
for (; k && path[k-1]!='/'; k--);
lev.base = k;
}
if (type == FTW_D || type == FTW_DP) {
dfd = open(path, O_RDONLY | O_DIRECTORY);
err = errno;
if (dfd < 0 && err == EACCES) type = FTW_DNR;
if (!fd_limit) close(dfd);
}
if (!(flags & FTW_DEPTH) && (r=fn(path, &st, type, &lev)))
return r;
for (; h; h = h->chain)
if (h->dev == st.st_dev && h->ino == st.st_ino)
return 0;
if ((type == FTW_D || type == FTW_DP) && fd_limit) {
if (dfd < 0) {
errno = err;
return -1;
}
DIR *d = fdopendir(dfd);
if (d) {
struct dirent *de;
while ((de = readdir(d))) {
if (de->d_name[0] == '.'
&& (!de->d_name[1]
|| (de->d_name[1]=='.'
&& !de->d_name[2]))) continue;
if (strlen(de->d_name) >= PATH_MAXIMUS-l) {
errno = ENAMETOOLONG;
closedir(d);
return -1;
}
path[j]='/';
strcpy(path+j+1, de->d_name);
if ((r=do_nftw(path, fn, fd_limit-1, flags, &new))) {
closedir(d);
return r;
}
}
closedir(d);
} else {
close(dfd);
return -1;
}
}
path[l] = 0;
if ((flags & FTW_DEPTH) && (r=fn(path, &st, type, &lev)))
return r;
return 0;
}
/**
* Walks file tree.
*
* @return 0 on success, -1 on error, or non-zero `fn` result
* @see examples/walk.c for example
*/
int nftw(const char *dirpath,
int fn(const char *fpath,
const struct stat *st,
int typeflag,
struct FTW *ftwbuf),
int fd_limit,
int flags)
{
int r, cs;
size_t l;
char pathbuf[PATH_MAXIMUS+1];
if (fd_limit <= 0) return 0;
l = strlen(dirpath);
if (l > PATH_MAXIMUS) {
errno = ENAMETOOLONG;
return -1;
}
memcpy(pathbuf, dirpath, l+1);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
r = do_nftw(pathbuf, fn, fd_limit, flags, NULL);
pthread_setcancelstate(cs, 0);
return r;
}

View file

@ -33,7 +33,7 @@
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/clock.h"
#include "third_party/musl/ftw.h"
#include "libc/stdio/ftw.h"
#include "third_party/quickjs/cutils.h"
#include "third_party/quickjs/list.h"
#include "third_party/quickjs/quickjs-libc.h"

66
third_party/tr/tr.c vendored
View file

@ -1,34 +1,40 @@
/* $OpenBSD: tr.c,v 1.21 2022/02/11 16:09:21 cheloha Exp $ */
/* $NetBSD: tr.c,v 1.5 1995/08/31 22:13:48 jtc Exp $ */
/*
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi
$OpenBSD: tr.c,v 1.21 2022/02/11 16:09:21 cheloha Exp $
$NetBSD: tr.c,v 1.5 1995/08/31 22:13:48 jtc Exp $
Copyright (c) 1988, 1993
The Regents of the University of California. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#include "libc/calls/calls.h"
#include "libc/intrin/kprintf.h"
#include "libc/log/bsd.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"

View file

@ -42,7 +42,7 @@ $(THIRD_PARTY_TR_A).pkg: \
o/$(MODE)/third_party/tr/tr.com.dbg: \
$(THIRD_PARTY_TR) \
o/$(MODE)/third_party/tr/tr.o \
o/$(MODE)/third_party/tr/cmd.o \
$(CRT) \
$(APE_NO_MODIFY_SELF)
@$(APELINK)