mirror of
https://github.com/krgamestudios/Toy.git
synced 2026-04-15 23:04:08 +10:00
Missed a rename
This commit is contained in:
@@ -88,7 +88,7 @@ void repl() {
|
||||
|
||||
//entry point
|
||||
int main(int argc, const char* argv[]) {
|
||||
Toy_initCommand(argc, argv);
|
||||
Toy_initCommandLine(argc, argv);
|
||||
|
||||
//lib setup (hacky - only really for this program)
|
||||
Toy_initDriveDictionary();
|
||||
@@ -101,30 +101,30 @@ int main(int argc, const char* argv[]) {
|
||||
Toy_freeLiteral(driveLiteral);
|
||||
Toy_freeLiteral(pathLiteral);
|
||||
|
||||
//command specific actions
|
||||
if (command.error) {
|
||||
Toy_usageCommand(argc, argv);
|
||||
//Toy_commandLine specific actions
|
||||
if (Toy_commandLine.error) {
|
||||
Toy_usageCommandLine(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command.help) {
|
||||
Toy_helpCommand(argc, argv);
|
||||
if (Toy_commandLine.help) {
|
||||
Toy_helpCommandLine(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command.version) {
|
||||
Toy_copyrightCommand(argc, argv);
|
||||
if (Toy_commandLine.version) {
|
||||
Toy_copyrightCommandLine(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//version
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf(TOY_CC_NOTICE "Toy Programming Language Version %d.%d.%d\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH);
|
||||
}
|
||||
|
||||
//run source file
|
||||
if (command.sourcefile) {
|
||||
Toy_runSourceFile(command.sourcefile);
|
||||
if (Toy_commandLine.sourcefile) {
|
||||
Toy_runSourceFile(Toy_commandLine.sourcefile);
|
||||
|
||||
//lib cleanup
|
||||
Toy_freeDriveDictionary();
|
||||
@@ -133,8 +133,8 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
|
||||
//run from stdin
|
||||
if (command.source) {
|
||||
Toy_runSource(command.source);
|
||||
if (Toy_commandLine.source) {
|
||||
Toy_runSource(Toy_commandLine.source);
|
||||
|
||||
//lib cleanup
|
||||
Toy_freeDriveDictionary();
|
||||
@@ -143,20 +143,20 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
|
||||
//compile source file
|
||||
if (command.compilefile && command.outfile) {
|
||||
if (Toy_commandLine.compilefile && Toy_commandLine.outfile) {
|
||||
size_t size = 0;
|
||||
char* source = Toy_readFile(command.compilefile, &size);
|
||||
char* source = Toy_readFile(Toy_commandLine.compilefile, &size);
|
||||
unsigned char* tb = Toy_compileString(source, &size);
|
||||
if (!tb) {
|
||||
return 1;
|
||||
}
|
||||
Toy_writeFile(command.outfile, tb, size);
|
||||
Toy_writeFile(Toy_commandLine.outfile, tb, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//run binary
|
||||
if (command.binaryfile) {
|
||||
Toy_runBinaryFile(command.binaryfile);
|
||||
if (Toy_commandLine.binaryfile) {
|
||||
Toy_runBinaryFile(Toy_commandLine.binaryfile);
|
||||
|
||||
//lib cleanup
|
||||
Toy_freeDriveDictionary();
|
||||
|
||||
@@ -18,74 +18,74 @@ STATIC_ASSERT(sizeof(unsigned int) == 4);
|
||||
#ifndef TOY_EXPORT
|
||||
|
||||
//declare the singleton
|
||||
Command command;
|
||||
Toy_CommandLine Toy_commandLine;
|
||||
|
||||
void Toy_initCommand(int argc, const char* argv[]) {
|
||||
void Toy_initCommandLine(int argc, const char* argv[]) {
|
||||
//default values
|
||||
command.error = false;
|
||||
command.help = false;
|
||||
command.version = false;
|
||||
command.binaryfile = NULL;
|
||||
command.sourcefile = NULL;
|
||||
command.compilefile = NULL;
|
||||
command.outfile = "out.tb";
|
||||
command.source = NULL;
|
||||
command.verbose = false;
|
||||
Toy_commandLine.error = false;
|
||||
Toy_commandLine.help = false;
|
||||
Toy_commandLine.version = false;
|
||||
Toy_commandLine.binaryfile = NULL;
|
||||
Toy_commandLine.sourcefile = NULL;
|
||||
Toy_commandLine.compilefile = NULL;
|
||||
Toy_commandLine.outfile = "out.tb";
|
||||
Toy_commandLine.source = NULL;
|
||||
Toy_commandLine.verbose = false;
|
||||
|
||||
for (int i = 1; i < argc; i++) { //start at 1 to skip the program name
|
||||
command.error = true; //error state by default, set to false by successful flags
|
||||
Toy_commandLine.error = true; //error state by default, set to false by successful flags
|
||||
|
||||
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||
command.help = true;
|
||||
command.error = false;
|
||||
Toy_commandLine.help = true;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) {
|
||||
command.version = true;
|
||||
command.error = false;
|
||||
Toy_commandLine.version = true;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
|
||||
command.verbose = true;
|
||||
command.error = false;
|
||||
Toy_commandLine.verbose = true;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!strcmp(argv[i], "-f") || !strcmp(argv[i], "--sourcefile")) && i + 1 < argc) {
|
||||
command.sourcefile = (char*)argv[i + 1];
|
||||
Toy_commandLine.sourcefile = (char*)argv[i + 1];
|
||||
i++;
|
||||
command.error = false;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!strcmp(argv[i], "-i") || !strcmp(argv[i], "--input")) && i + 1 < argc) {
|
||||
command.source = (char*)argv[i + 1];
|
||||
Toy_commandLine.source = (char*)argv[i + 1];
|
||||
i++;
|
||||
command.error = false;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compile")) && i + 1 < argc) {
|
||||
command.compilefile = (char*)argv[i + 1];
|
||||
Toy_commandLine.compilefile = (char*)argv[i + 1];
|
||||
i++;
|
||||
command.error = false;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) && i + 1 < argc) {
|
||||
command.outfile = (char*)argv[i + 1];
|
||||
Toy_commandLine.outfile = (char*)argv[i + 1];
|
||||
i++;
|
||||
command.error = false;
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
//option without a flag + ending in .tb = binary input
|
||||
if (i < argc) {
|
||||
if (strncmp(&(argv[i][strlen(argv[i]) - 3]), ".tb", 3) == 0) {
|
||||
command.binaryfile = (char*)argv[i];
|
||||
command.error = false;
|
||||
Toy_commandLine.binaryfile = (char*)argv[i];
|
||||
Toy_commandLine.error = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -95,12 +95,12 @@ void Toy_initCommand(int argc, const char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
void Toy_usageCommand(int argc, const char* argv[]) {
|
||||
void Toy_usageCommandLine(int argc, const char* argv[]) {
|
||||
printf("Usage: %s [<file.tb> | -h | -v | [-d][-f file | -i source | -c file [-o outfile]]]\n\n", argv[0]);
|
||||
}
|
||||
|
||||
void Toy_helpCommand(int argc, const char* argv[]) {
|
||||
Toy_usageCommand(argc, argv);
|
||||
void Toy_helpCommandLine(int argc, const char* argv[]) {
|
||||
Toy_usageCommandLine(argc, argv);
|
||||
|
||||
printf("<file.tb>\t\t\tBinary input file in tb format, must be version %d.%d.%d.\n\n", TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH);
|
||||
printf("-h\t| --help\t\tShow this help then exit.\n\n");
|
||||
@@ -112,7 +112,7 @@ void Toy_helpCommand(int argc, const char* argv[]) {
|
||||
printf("-o\t| --output outfile\tName of the output file built with --compile (default: out.tb).\n\n");
|
||||
}
|
||||
|
||||
void Toy_copyrightCommand(int argc, const char* argv[]) {
|
||||
void Toy_copyrightCommandLine(int argc, const char* argv[]) {
|
||||
printf("Toy Programming Language Interpreter Version %d.%d.%d (built on %s)\n\n", TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, TOY_VERSION_BUILD);
|
||||
printf("Copyright (c) 2020-2022 Kayne Ruse, KR Game Studios\n\n");
|
||||
printf("This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.\n\n");
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
//for processing the command line arguments
|
||||
//for processing the Toy_commandLine line arguments
|
||||
typedef struct {
|
||||
bool error;
|
||||
bool help;
|
||||
@@ -33,13 +33,13 @@ typedef struct {
|
||||
char* outfile; //defaults to out.tb
|
||||
char* source;
|
||||
bool verbose;
|
||||
} Command;
|
||||
} Toy_CommandLine;
|
||||
|
||||
extern Command command;
|
||||
extern Toy_CommandLine Toy_commandLine;
|
||||
|
||||
void Toy_initCommand(int argc, const char* argv[]);
|
||||
void Toy_initCommandLine(int argc, const char* argv[]);
|
||||
|
||||
void Toy_usageCommand(int argc, const char* argv[]);
|
||||
void Toy_helpCommand(int argc, const char* argv[]);
|
||||
void Toy_copyrightCommand(int argc, const char* argv[]);
|
||||
void Toy_usageCommandLine(int argc, const char* argv[]);
|
||||
void Toy_helpCommandLine(int argc, const char* argv[]);
|
||||
void Toy_copyrightCommandLine(int argc, const char* argv[]);
|
||||
#endif
|
||||
|
||||
@@ -279,7 +279,7 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_UNARY: {
|
||||
//pass to the child node, then embed the unary command (print, negate, etc.)
|
||||
//pass to the child node, then embed the unary Toy_commandLine (print, negate, etc.)
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, node->unary.child, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
@@ -292,7 +292,7 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
|
||||
//all infixes come here
|
||||
case TOY_AST_NODE_BINARY: {
|
||||
//pass to the child nodes, then embed the binary command (math, etc.)
|
||||
//pass to the child nodes, then embed the binary Toy_commandLine (math, etc.)
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, node->binary.left, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
|
||||
//special case for when indexing and assigning
|
||||
|
||||
@@ -1559,7 +1559,7 @@ static bool execIndex(Toy_Interpreter* interpreter, bool assignIntermediate) {
|
||||
Toy_pushLiteralArray(&arguments, first);
|
||||
Toy_pushLiteralArray(&arguments, second);
|
||||
Toy_pushLiteralArray(&arguments, third);
|
||||
Toy_pushLiteralArray(&arguments, TOY_TO_NULL_LITERAL); //it expects an assignment command
|
||||
Toy_pushLiteralArray(&arguments, TOY_TO_NULL_LITERAL); //it expects an assignment Toy_commandLine
|
||||
Toy_pushLiteralArray(&arguments, TOY_TO_NULL_LITERAL); //it expects an assignment "opcode"
|
||||
|
||||
//leave the idn and compound on the stack
|
||||
@@ -1689,7 +1689,7 @@ static bool execIndexAssign(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&arguments, first);
|
||||
Toy_pushLiteralArray(&arguments, second);
|
||||
Toy_pushLiteralArray(&arguments, third);
|
||||
Toy_pushLiteralArray(&arguments, assign); //it expects an assignment command
|
||||
Toy_pushLiteralArray(&arguments, assign); //it expects an assignment Toy_commandLine
|
||||
Toy_pushLiteralArray(&arguments, op); //it expects an assignment "opcode"
|
||||
|
||||
//call the _index function
|
||||
@@ -2052,7 +2052,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
const unsigned short literalCount = readShort(interpreter->bytecode, &interpreter->count);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf(TOY_CC_NOTICE "Reading %d literals\n" TOY_CC_RESET, literalCount);
|
||||
}
|
||||
#endif
|
||||
@@ -2066,7 +2066,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, TOY_TO_NULL_LITERAL);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(null)\n");
|
||||
}
|
||||
#endif
|
||||
@@ -2080,7 +2080,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_freeLiteral(literal);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(boolean %s)\n", b ? "true" : "false");
|
||||
}
|
||||
#endif
|
||||
@@ -2094,7 +2094,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_freeLiteral(literal);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(integer %d)\n", d);
|
||||
}
|
||||
#endif
|
||||
@@ -2108,7 +2108,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_freeLiteral(literal);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(float %f)\n", f);
|
||||
}
|
||||
#endif
|
||||
@@ -2123,7 +2123,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_freeLiteral(literal);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(string \"%s\")\n", s);
|
||||
}
|
||||
#endif
|
||||
@@ -2143,7 +2143,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
}
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(array ");
|
||||
Toy_Literal literal = TOY_TO_ARRAY_LITERAL(array);
|
||||
Toy_printLiteral(literal);
|
||||
@@ -2174,7 +2174,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
}
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(dictionary ");
|
||||
Toy_Literal literal = TOY_TO_DICTIONARY_LITERAL(dictionary);
|
||||
Toy_printLiteral(literal);
|
||||
@@ -2203,7 +2203,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, literal);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(function)\n");
|
||||
}
|
||||
#endif
|
||||
@@ -2219,7 +2219,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, identifier);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(identifier %s (hash: %x))\n", Toy_toCString(TOY_AS_IDENTIFIER(identifier)), identifier.as.identifier.hash);
|
||||
}
|
||||
#endif
|
||||
@@ -2239,7 +2239,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, typeLiteral);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(type ");
|
||||
Toy_printLiteral(typeLiteral);
|
||||
printf(")\n");
|
||||
@@ -2274,7 +2274,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, typeLiteral); //copied
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(type ");
|
||||
Toy_printLiteral(typeLiteral);
|
||||
printf(")\n");
|
||||
@@ -2290,7 +2290,7 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, TOY_TO_INDEX_BLANK_LITERAL);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(blank)\n");
|
||||
}
|
||||
#endif
|
||||
@@ -2388,7 +2388,7 @@ void Toy_runInterpreter(Toy_Interpreter* interpreter, unsigned char* bytecode, i
|
||||
const char* build = readString(interpreter->bytecode, &interpreter->count);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
if (strncmp(build, TOY_VERSION_BUILD, strlen(TOY_VERSION_BUILD))) {
|
||||
printf(TOY_CC_WARN "Warning: interpreter/bytecode build mismatch\n" TOY_CC_RESET);
|
||||
}
|
||||
@@ -2402,7 +2402,7 @@ void Toy_runInterpreter(Toy_Interpreter* interpreter, unsigned char* bytecode, i
|
||||
|
||||
//code section
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf(TOY_CC_NOTICE "executing bytecode\n" TOY_CC_RESET);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -110,7 +110,7 @@ static Toy_Token makeErrorToken(Toy_Lexer* lexer, char* msg) {
|
||||
token.line = lexer->line;
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("err:");
|
||||
Toy_printToken(&token);
|
||||
}
|
||||
@@ -129,7 +129,7 @@ static Toy_Token makeToken(Toy_Lexer* lexer, Toy_TokenType type) {
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
//BUG #10: this shows TOKEN_EOF twice due to the overarching structure of the program - can't be fixed
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("tok:");
|
||||
Toy_printToken(&token);
|
||||
}
|
||||
@@ -157,7 +157,7 @@ static Toy_Token makeIntegerOrFloat(Toy_Lexer* lexer) {
|
||||
token.line = lexer->line;
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
if (type == TOY_TOKEN_LITERAL_INTEGER) {
|
||||
printf("int:");
|
||||
} else {
|
||||
@@ -189,7 +189,7 @@ static Toy_Token makeString(Toy_Lexer* lexer, char terminator) {
|
||||
token.line = lexer->line;
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("str:");
|
||||
Toy_printToken(&token);
|
||||
}
|
||||
@@ -216,7 +216,7 @@ static Toy_Token makeKeywordOrIdentifier(Toy_Lexer* lexer) {
|
||||
token.line = lexer->line;
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("kwd:");
|
||||
Toy_printToken(&token);
|
||||
}
|
||||
@@ -235,7 +235,7 @@ static Toy_Token makeKeywordOrIdentifier(Toy_Lexer* lexer) {
|
||||
token.line = lexer->line;
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("idf:");
|
||||
Toy_printToken(&token);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ static void consume(Toy_Parser* parser, Toy_TokenType tokenType, const char* msg
|
||||
|
||||
static void synchronize(Toy_Parser* parser) {
|
||||
#ifndef TOY_EXPORT
|
||||
if (command.verbose) {
|
||||
if (Toy_commandLine.verbose) {
|
||||
fprintf(stderr, TOY_CC_ERROR "synchronizing\n" TOY_CC_RESET);
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user