ADDED src/Alias.h Index: src/Alias.h ================================================================== --- /dev/null +++ src/Alias.h @@ -0,0 +1,15 @@ +#import "Identifier.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface Alias : Identifier +@property (copy, nonatomic) OFString *action; +@property (readonly, nonatomic) bool persisted; + +- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE; +- (instancetype)initWithName:(OFString *)name + action:(OFString *)action + persisted:(bool)persisted; +@end + +OF_ASSUME_NONNULL_END ADDED src/Alias.m Index: src/Alias.m ================================================================== --- /dev/null +++ src/Alias.m @@ -0,0 +1,15 @@ +#import "Alias.h" + +@implementation Alias +- (instancetype)initWithName:(OFString *)name + action:(OFString *)action + persisted:(bool)persisted +{ + self = [super initWithName:name]; + + _action = [action copy]; + _persisted = persisted; + + return self; +} +@end ADDED src/Command.h Index: src/Command.h ================================================================== --- /dev/null +++ src/Command.h @@ -0,0 +1,18 @@ +#import "Identifier.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface Command : Identifier +@property (readonly, nonatomic) void (*function)(); +@property (readonly, nonatomic) int argumentsTypes; + +- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE; +- (instancetype)initWithName:(OFString *)name + function:(void (*)())function + argumentsTypes:(int)argumentsTypes; +- (int)callWithArguments:(char *_Nonnull *_Nonnull)arguments + numArguments:(size_t)numArguments + isDown:(bool)isDown; +@end + +OF_ASSUME_NONNULL_END ADDED src/Command.mm Index: src/Command.mm ================================================================== --- /dev/null +++ src/Command.mm @@ -0,0 +1,131 @@ +#import "Command.h" + +#include + +@implementation Command +- (instancetype)initWithName:(OFString *)name + function:(void (*)())function + argumentsTypes:(int)argumentsTypes +{ + self = [super initWithName:name]; + + _function = function; + _argumentsTypes = argumentsTypes; + + return self; +} + +- (int)callWithArguments:(char **)arguments + numArguments:(size_t)numArguments + isDown:(bool)isDown +{ + switch (_argumentsTypes) { + case ARG_1INT: + if (isDown) + ((void(__cdecl *)(int))_function)(ATOI(arguments[1])); + break; + case ARG_2INT: + if (isDown) + ((void(__cdecl *)(int, int))_function)( + ATOI(arguments[1]), ATOI(arguments[2])); + break; + case ARG_3INT: + if (isDown) + ((void(__cdecl *)(int, int, int))_function)( + ATOI(arguments[1]), ATOI(arguments[2]), + ATOI(arguments[3])); + break; + case ARG_4INT: + if (isDown) + ((void(__cdecl *)(int, int, int, int))_function)( + ATOI(arguments[1]), ATOI(arguments[2]), + ATOI(arguments[3]), ATOI(arguments[4])); + break; + case ARG_NONE: + if (isDown) + ((void(__cdecl *)())_function)(); + break; + case ARG_1STR: + if (isDown) { + @autoreleasepool { + ((void(__cdecl *)(OFString *))_function)( + @(arguments[1])); + } + } + break; + case ARG_2STR: + if (isDown) { + @autoreleasepool { + ((void(__cdecl *)( + OFString *, OFString *))_function)( + @(arguments[1]), @(arguments[2])); + } + } + break; + case ARG_3STR: + if (isDown) { + @autoreleasepool { + ((void(__cdecl *)(OFString *, OFString *, + OFString *))_function)(@(arguments[1]), + @(arguments[2]), @(arguments[3])); + } + } + break; + case ARG_5STR: + if (isDown) { + @autoreleasepool { + ((void(__cdecl *)(OFString *, OFString *, + OFString *, OFString *, + OFString *))_function)(@(arguments[1]), + @(arguments[2]), @(arguments[3]), + @(arguments[4]), @(arguments[5])); + } + } + break; + case ARG_DOWN: + ((void(__cdecl *)(bool))_function)(isDown); + break; + case ARG_DWN1: + ((void(__cdecl *)(bool, char *))_function)( + isDown, arguments[1]); + break; + case ARG_1EXP: + if (isDown) + return ((int(__cdecl *)(int))_function)( + execute(arguments[1])); + break; + case ARG_2EXP: + if (isDown) + return ((int(__cdecl *)(int, int))_function)( + execute(arguments[1]), execute(arguments[2])); + break; + case ARG_1EST: + if (isDown) + return ((int(__cdecl *)(char *))_function)( + arguments[1]); + break; + case ARG_2EST: + if (isDown) + return ((int(__cdecl *)(char *, char *))_function)( + arguments[1], arguments[2]); + break; + case ARG_VARI: + if (isDown) { + // limit, remove + string r; + r[0] = 0; + for (int i = 1; i < numArguments; i++) { + // make string-list out of all arguments + strcat_s(r, arguments[i]); + if (i == numArguments - 1) + break; + strcat_s(r, " "); + } + ((void(__cdecl *)(char *))_function)(r); + } + break; + } + + return 0; +} +@end DELETED src/Ident.h Index: src/Ident.h ================================================================== --- src/Ident.h +++ /dev/null @@ -1,18 +0,0 @@ -#import - -OF_ASSUME_NONNULL_BEGIN - -enum IdentType { ID_VAR, ID_COMMAND, ID_ALIAS }; - -@interface Ident : OFObject -@property (nonatomic) enum IdentType type; -@property (copy, nonatomic) OFString *name; -@property (nonatomic) int min, max; // ID_VAR -@property (nonatomic) int *storage; // ID_VAR -@property (nonatomic) void (*fun)(); // ID_VAR, ID_COMMAND -@property (nonatomic) int narg; // ID_VAR, ID_COMMAND -@property (copy, nonatomic) OFString *action; // ID_ALIAS -@property (nonatomic) bool persist; -@end - -OF_ASSUME_NONNULL_END DELETED src/Ident.m Index: src/Ident.m ================================================================== --- src/Ident.m +++ /dev/null @@ -1,4 +0,0 @@ -#import "Ident.h" - -@implementation Ident -@end ADDED src/Identifier.h Index: src/Identifier.h ================================================================== --- /dev/null +++ src/Identifier.h @@ -0,0 +1,12 @@ +#import + +OF_ASSUME_NONNULL_BEGIN + +@interface Identifier : OFObject +@property (readonly, copy, nonatomic) OFString *name; + +- (instancetype)init OF_UNAVAILABLE; +- (instancetype)initWithName:(OFString *)name; +@end + +OF_ASSUME_NONNULL_END ADDED src/Identifier.m Index: src/Identifier.m ================================================================== --- /dev/null +++ src/Identifier.m @@ -0,0 +1,12 @@ +#import "Identifier.h" + +@implementation Identifier +- (instancetype)initWithName:(OFString *)name +{ + self = [super init]; + + _name = [name copy]; + + return self; +} +@end ADDED src/Variable.h Index: src/Variable.h ================================================================== --- /dev/null +++ src/Variable.h @@ -0,0 +1,20 @@ +#import "Identifier.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface Variable : Identifier +@property (readonly, nonatomic) int min, max; +@property (readonly, nonatomic) int *storage; +@property (readonly, nonatomic) void (*function)(); +@property (readonly, nonatomic) bool persisted; + +- (instancetype)initWithName:(OFString *)name OF_UNAVAILABLE; +- (instancetype)initWithName:(OFString *)name + min:(int)min + max:(int)max + storage:(int *)storage + function:(void (*)())function + persisted:(bool)persisted; +@end + +OF_ASSUME_NONNULL_END ADDED src/Variable.m Index: src/Variable.m ================================================================== --- /dev/null +++ src/Variable.m @@ -0,0 +1,21 @@ +#import "Variable.h" + +@implementation Variable +- (instancetype)initWithName:(OFString *)name + min:(int)min + max:(int)max + storage:(int *)storage + function:(void (*)())function + persisted:(bool)persisted +{ + self = [super initWithName:name]; + + _min = min; + _max = max; + _storage = storage; + _function = function; + _persisted = persisted; + + return self; +} +@end DELETED src/command.mm Index: src/command.mm ================================================================== --- src/command.mm +++ /dev/null @@ -1,815 +0,0 @@ -// command.cpp: implements the parsing and execution of a tiny script language -// which is largely backwards compatible with the quake console language. - -#include "cube.h" - -#include - -#import "Ident.h" - -void -itoa(char *s, int i) -{ - sprintf_s(s)("%d", i); -} - -char * -exchangestr(char *o, const char *n) -{ - gp()->deallocstr(o); - return newstring(n); -} - -// contains ALL vars/commands/aliases -OFMutableDictionary *idents; - -void -alias(OFString *name, OFString *action) -{ - Ident *b = idents[name]; - - if (b == nil) { - Ident *b = [[Ident alloc] init]; - b.type = ID_ALIAS; - b.name = name; - b.action = action; - b.persist = true; - - idents[b.name] = b; - } else { - if (b.type == ID_ALIAS) - b.action = action; - else - conoutf( - @"cannot redefine builtin %@ with an alias", name); - } -} -COMMAND(alias, ARG_2STR) - -int -variable(OFString *name, int min, int cur, int max, int *storage, void (*fun)(), - bool persist) -{ - if (idents == nil) - idents = [[OFMutableDictionary alloc] init]; - - Ident *v = [[Ident alloc] init]; - v.type = ID_VAR; - v.name = name; - v.min = min; - v.max = max; - v.storage = storage; - v.fun = fun; - v.persist = persist; - - idents[name] = v; - - return cur; -} - -void -setvar(OFString *name, int i) -{ - *idents[name].storage = i; -} - -int -getvar(OFString *name) -{ - return *idents[name].storage; -} - -bool -identexists(OFString *name) -{ - return (idents[name] != nil); -} - -OFString * -getalias(OFString *name) -{ - Ident *i = idents[name]; - return i != nil && i.type == ID_ALIAS ? i.action : nil; -} - -bool -addcommand(OFString *name, void (*fun)(), int narg) -{ - if (idents == nil) - idents = [[OFMutableDictionary alloc] init]; - - @autoreleasepool { - Ident *c = [[Ident alloc] init]; - c.type = ID_COMMAND; - c.name = name; - c.fun = fun; - c.narg = narg; - - idents[name] = c; - } - - return false; -} - -char * -parseexp(char *&p, int right) // parse any nested set of () or [] -{ - int left = *p++; - char *word = p; - for (int brak = 1; brak;) { - int c = *p++; - if (c == '\r') - *(p - 1) = ' '; // hack - if (c == left) - brak++; - else if (c == right) - brak--; - else if (!c) { - p--; - conoutf(@"missing \"%c\"", right); - return NULL; - } - } - char *s = newstring(word, p - word - 1); - if (left == '(') { - string t; - itoa(t, - execute( - s)); // evaluate () exps directly, and substitute result - s = exchangestr(s, t); - } - return s; -} - -char * -parseword(char *&p) // parse single argument, including expressions -{ - p += strspn(p, " \t\r"); - if (p[0] == '/' && p[1] == '/') - p += strcspn(p, "\n\0"); - if (*p == '\"') { - p++; - char *word = p; - p += strcspn(p, "\"\r\n\0"); - char *s = newstring(word, p - word); - if (*p == '\"') - p++; - return s; - } - if (*p == '(') - return parseexp(p, ')'); - if (*p == '[') - return parseexp(p, ']'); - char *word = p; - p += strcspn(p, "; \t\r\n\0"); - if (p - word == 0) - return NULL; - return newstring(word, p - word); -} - -char * -lookup(char *n) // find value of ident referenced with $ in exp -{ - @autoreleasepool { - Ident *ID = idents[@(n + 1)]; - - if (ID != nil) { - switch (ID.type) { - case ID_VAR: - string t; - itoa(t, *(ID.storage)); - return exchangestr(n, t); - case ID_ALIAS: - return exchangestr(n, ID.action.UTF8String); - } - } - } - - conoutf(@"unknown alias lookup: %s", n + 1); - return n; -} - -int -execute(char *p, bool isdown) // all evaluation happens here, recursively -{ - const int MAXWORDS = 25; // limit, remove - char *w[MAXWORDS]; - int val = 0; - for (bool cont = true; cont;) // for each ; seperated statement - { - int numargs = MAXWORDS; - loopi(MAXWORDS) // collect all argument values - { - w[i] = ""; - if (i > numargs) - continue; - char *s = parseword(p); // parse and evaluate exps - if (!s) { - numargs = i; - s = ""; - } - if (*s == '$') - s = lookup(s); // substitute variables - w[i] = s; - } - - p += strcspn(p, ";\n\0"); - cont = *p++ != - 0; // more statements if this isn't the end of the string - char *c = w[0]; - if (*c == '/') - c++; // strip irc-style command prefix - if (!*c) - continue; // empty statement - - @autoreleasepool { - Ident *ID = idents[@(c)]; - - if (ID == nil) { - val = ATOI(c); - if (!val && *c != '0') - conoutf(@"unknown command: %s", c); - } else { - switch (ID.type) { - // game defined commands - case ID_COMMAND: - // use very ad-hoc function signature, - // and just call it - switch (ID.narg) { - case ARG_1INT: - if (isdown) - ((void(__cdecl *)( - int))ID.fun)( - ATOI(w[1])); - break; - case ARG_2INT: - if (isdown) - ((void(__cdecl *)( - int, int))ID.fun)( - ATOI(w[1]), - ATOI(w[2])); - break; - case ARG_3INT: - if (isdown) - ((void(__cdecl *)(int, - int, int))ID.fun)( - ATOI(w[1]), - ATOI(w[2]), - ATOI(w[3])); - break; - case ARG_4INT: - if (isdown) - ((void(__cdecl *)(int, - int, int, - int))ID.fun)( - ATOI(w[1]), - ATOI(w[2]), - ATOI(w[3]), - ATOI(w[4])); - break; - case ARG_NONE: - if (isdown) - ((void(__cdecl *)()) - ID.fun)(); - break; - case ARG_1STR: - if (isdown) { - @autoreleasepool { - ((void( - __cdecl *)( - OFString *)) - ID.fun)( - @(w[1])); - } - } - break; - case ARG_2STR: - if (isdown) { - @autoreleasepool { - ((void( - __cdecl *)( - OFString *, - OFString *)) - ID.fun)( - @(w[1]), - @(w[2])); - } - } - break; - case ARG_3STR: - if (isdown) { - @autoreleasepool { - ((void( - __cdecl *)( - OFString *, - OFString *, - OFString *)) - ID.fun)( - @(w[1]), - @(w[2]), - @(w[3])); - } - } - break; - case ARG_5STR: - if (isdown) { - @autoreleasepool { - ((void( - __cdecl *)( - OFString *, - OFString *, - OFString *, - OFString *, - OFString *)) - ID.fun)( - @(w[1]), - @(w[2]), - @(w[3]), - @(w[4]), - @(w[5])); - } - } - break; - case ARG_DOWN: - ((void(__cdecl *)(bool))ID.fun)( - isdown); - break; - case ARG_DWN1: - ((void(__cdecl *)( - bool, char *))ID.fun)( - isdown, w[1]); - break; - case ARG_1EXP: - if (isdown) - val = ((int(__cdecl *)( - int))ID.fun)( - execute(w[1])); - break; - case ARG_2EXP: - if (isdown) - val = ((int(__cdecl *)( - int, int))ID.fun)( - execute(w[1]), - execute(w[2])); - break; - case ARG_1EST: - if (isdown) - val = ((int(__cdecl *)( - char *))ID.fun)( - w[1]); - break; - case ARG_2EST: - if (isdown) - val = ((int(__cdecl *)( - char *, - char *))ID.fun)( - w[1], w[2]); - break; - case ARG_VARI: - if (isdown) { - // limit, remove - string r; - r[0] = 0; - for (int i = 1; - i < numargs; i++) { - // make - // string-list - // out of all - // arguments - strcat_s( - r, w[i]); - if (i == - numargs - 1) - break; - strcat_s( - r, " "); - } - ((void(__cdecl *)( - char *))ID.fun)(r); - break; - } - } - break; - - // game defined variables - case ID_VAR: - if (isdown) { - if (!w[1][0]) - // var with no value - // just prints its - // current value - conoutf(@"%s = %d", c, - *ID.storage); - else { - if (ID.min > ID.max) { - conoutf( - @"variable " - @"is " - @"read-" - @"only"); - } else { - int i1 = - ATOI(w[1]); - if (i1 < - ID.min || - i1 > - ID.max) { - // clamp - // to - // valid - // range - i1 = - i1 < ID.min - ? ID.min - : ID.max; - conoutf( - @"v" - @"a" - @"l" - @"i" - @"d" - @" " - @"r" - @"a" - @"n" - @"g" - @"e" - @" " - @"f" - @"o" - @"r" - @" " - @"%" - @"s" - @" " - @"i" - @"s" - @" " - @"%" - @"d" - @"." - @"." - @"%" - @"d", - c, - ID.min, - ID.max); - } - *ID.storage = - i1; - } - if (ID.fun) - // call trigger - // function if - // available - ((void(__cdecl - *)())ID - .fun)(); - } - } - break; - - // alias, also used as functions and (global) - // variables - case ID_ALIAS: - for (int i = 1; i < numargs; i++) { - @autoreleasepool { - // set any arguments as - // (global) arg values - // so functions can - // access them - OFString *t = [OFString - stringWithFormat: - @"arg%d", i]; - alias(t, @(w[i])); - } - } - // create new string here because alias - // could rebind itself - char *action = - newstring(ID.action.UTF8String); - val = execute(action, isdown); - gp()->deallocstr(action); - break; - } - } - } - loopj(numargs) gp()->deallocstr(w[j]); - } - - return val; -} - -// tab-completion of all idents - -int completesize = 0, completeidx = 0; - -void -resetcomplete() -{ - completesize = 0; -} - -void -complete(OFString *s_) -{ - @autoreleasepool { - std::unique_ptr copy(strdup(s_.UTF8String)); - char *s = copy.get(); - - if (*s != '/') { - string t; - strcpy_s(t, s); - strcpy_s(s, "/"); - strcat_s(s, t); - } - - if (!s[1]) - return; - - if (!completesize) { - completesize = strlen(s) - 1; - completeidx = 0; - } - - __block int idx = 0; - [idents enumerateKeysAndObjectsUsingBlock:^( - OFString *name, Ident *ident, bool *stop) { - if (strncmp(ident.name.UTF8String, s + 1, - completesize) == 0 && - idx++ == completeidx) { - strcpy_s(s, "/"); - strcat_s(s, ident.name.UTF8String); - } - }]; - - completeidx++; - - if (completeidx >= idx) - completeidx = 0; - } -} - -bool -execfile(OFString *cfgfile) -{ - @autoreleasepool { - OFMutableData *data; - @try { - data = [OFMutableData dataWithContentsOfFile:cfgfile]; - } @catch (id e) { - return false; - } - - // Ensure \0 termination. - [data addItem:""]; - - execute((char *)data.mutableItems); - return true; - } -} - -void -exec(OFString *cfgfile) -{ - if (!execfile(cfgfile)) { - @autoreleasepool { - conoutf(@"could not read \"%@\"", cfgfile); - } - } -} - -void -writecfg() -{ - OFStream *stream; - @try { - OFIRI *IRI = [Cube.sharedInstance.userDataIRI - IRIByAppendingPathComponent:@"config.cfg"]; - stream = [[OFIRIHandler handlerForIRI:IRI] openItemAtIRI:IRI - mode:@"w"]; - } @catch (id e) { - return; - } - - [stream writeString: - @"// automatically written on exit, do not modify\n" - @"// delete this file to have defaults.cfg overwrite these " - @"settings\n" - @"// modify settings in game, or put settings in " - @"autoexec.cfg to override anything\n" - @"\n"]; - writeclientinfo(stream); - [stream writeString:@"\n"]; - - [idents enumerateKeysAndObjectsUsingBlock:^( - OFString *name, Ident *ident, bool *stop) { - if (ident.type == ID_VAR && ident.persist) { - [stream - writeFormat:@"%@ %d\n", ident.name, *ident.storage]; - } - }]; - [stream writeString:@"\n"]; - - writebinds(stream); - [stream writeString:@"\n"]; - - [idents enumerateKeysAndObjectsUsingBlock:^( - OFString *name, Ident *ident, bool *stop) { - if (ident.type == ID_ALIAS && - ![ident.name hasPrefix:@"nextmap_"]) - [stream writeFormat:@"alias \"%@\" [%@]\n", ident.name, - ident.action]; - }]; - - [stream close]; -} - -COMMAND(writecfg, ARG_NONE) - -// below the commands that implement a small imperative language. thanks to the -// semantics of -// () and [] expressions, any control construct can be defined trivially. - -void -intset(OFString *name, int v) -{ - @autoreleasepool { - alias(name, [OFString stringWithFormat:@"%d", v]); - } -} - -void -ifthen(OFString *cond, OFString *thenp, OFString *elsep) -{ - @autoreleasepool { - std::unique_ptr cmd(strdup( - (cond.UTF8String[0] != '0' ? thenp : elsep).UTF8String)); - - execute(cmd.get()); - } -} - -void -loopa(OFString *times, OFString *body_) -{ - @autoreleasepool { - int t = (int)times.longLongValue; - std::unique_ptr body(strdup(body_.UTF8String)); - - loopi(t) - { - intset(@"i", i); - execute(body.get()); - } - } -} - -void -whilea(OFString *cond_, OFString *body_) -{ - @autoreleasepool { - std::unique_ptr cond(strdup(cond_.UTF8String)); - std::unique_ptr body(strdup(body_.UTF8String)); - - while (execute(cond.get())) - execute(body.get()); - } -} - -void -onrelease(bool on, char *body) -{ - if (!on) - execute(body); -} - -void -concat(char *s) -{ - @autoreleasepool { - alias(@"s", @(s)); - } -} - -void -concatword(char *s) -{ - for (char *a = s, *b = s; *a = *b; b++) - if (*a != ' ') - a++; - concat(s); -} - -int -listlen(char *a) -{ - if (!*a) - return 0; - int n = 0; - while (*a) - if (*a++ == ' ') - n++; - return n + 1; -} - -void -at(OFString *s_, OFString *pos) -{ - @autoreleasepool { - int n = (int)pos.longLongValue; - std::unique_ptr copy(strdup(s_.UTF8String)); - char *s = copy.get(); - - loopi(n) s += strspn(s += strcspn(s, " \0"), " "); - s[strcspn(s, " \0")] = 0; - concat(s); - } -} - -COMMANDN(loop, loopa, ARG_2STR) -COMMANDN(while, whilea, ARG_2STR) -COMMANDN(if, ifthen, ARG_3STR) -COMMAND(onrelease, ARG_DWN1) -COMMAND(exec, ARG_1STR) -COMMAND(concat, ARG_VARI) -COMMAND(concatword, ARG_VARI) -COMMAND(at, ARG_2STR) -COMMAND(listlen, ARG_1EST) - -int -add(int a, int b) -{ - return a + b; -} -COMMANDN(+, add, ARG_2EXP) - -int -mul(int a, int b) -{ - return a * b; -} -COMMANDN(*, mul, ARG_2EXP) - -int -sub(int a, int b) -{ - return a - b; -} -COMMANDN(-, sub, ARG_2EXP) - -int -divi(int a, int b) -{ - return b ? a / b : 0; -} -COMMANDN(div, divi, ARG_2EXP) - -int -mod(int a, int b) -{ - return b ? a % b : 0; -} -COMMAND(mod, ARG_2EXP) - -int -equal(int a, int b) -{ - return (int)(a == b); -} -COMMANDN(=, equal, ARG_2EXP) - -int -lt(int a, int b) -{ - return (int)(a < b); -} -COMMANDN(<, lt, ARG_2EXP) - -int -gt(int a, int b) -{ - return (int)(a > b); -} -COMMANDN(>, gt, ARG_2EXP) - -int -strcmpa(char *a, char *b) -{ - return strcmp(a, b) == 0; -} -COMMANDN(strcmp, strcmpa, ARG_2EST) - -int -rndn(int a) -{ - return a > 0 ? rnd(a) : 0; -} -COMMANDN(rnd, rndn, ARG_1EXP) - -int -explastmillis() -{ - return lastmillis; -} -COMMANDN(millis, explastmillis, ARG_1EXP) ADDED src/commands.mm Index: src/commands.mm ================================================================== --- /dev/null +++ src/commands.mm @@ -0,0 +1,678 @@ +// command.cpp: implements the parsing and execution of a tiny script language +// which is largely backwards compatible with the quake console language. + +#include "cube.h" + +#include + +#import "Alias.h" +#import "Command.h" +#import "Identifier.h" +#import "Variable.h" + +void +itoa(char *s, int i) +{ + sprintf_s(s)("%d", i); +} + +char * +exchangestr(char *o, const char *n) +{ + gp()->deallocstr(o); + return newstring(n); +} + +// contains ALL vars/commands/aliases +static OFMutableDictionary *identifiers; + +void +alias(OFString *name, OFString *action) +{ + Alias *alias = identifiers[name]; + + if (alias == nil) { + alias = [[Alias alloc] initWithName:name + action:action + persisted:true]; + + if (identifiers == nil) + identifiers = [[OFMutableDictionary alloc] init]; + + identifiers[name] = alias; + } else { + if ([alias isKindOfClass:[Alias class]]) + alias.action = action; + else + conoutf( + @"cannot redefine builtin %@ with an alias", name); + } +} +COMMAND(alias, ARG_2STR) + +int +variable(OFString *name, int min, int cur, int max, int *storage, + void (*function)(), bool persisted) +{ + Variable *variable = [[Variable alloc] initWithName:name + min:min + max:max + storage:storage + function:function + persisted:persisted]; + + if (identifiers == nil) + identifiers = [[OFMutableDictionary alloc] init]; + + identifiers[name] = variable; + + return cur; +} + +void +setvar(OFString *name, int i) +{ + *[identifiers[name] storage] = i; +} + +int +getvar(OFString *name) +{ + return *[identifiers[name] storage]; +} + +bool +identexists(OFString *name) +{ + return (identifiers[name] != nil); +} + +OFString * +getalias(OFString *name) +{ + Alias *alias = identifiers[name]; + + if ([alias isKindOfClass:[Alias class]]) + return alias.action; + + return nil; +} + +bool +addcommand(OFString *name, void (*function)(), int argumentsTypes) +{ + Command *command = [[Command alloc] initWithName:name + function:function + argumentsTypes:argumentsTypes]; + + if (identifiers == nil) + identifiers = [[OFMutableDictionary alloc] init]; + + identifiers[name] = command; + + return false; +} + +char * +parseexp(char *&p, int right) // parse any nested set of () or [] +{ + int left = *p++; + char *word = p; + for (int brak = 1; brak;) { + int c = *p++; + if (c == '\r') + *(p - 1) = ' '; // hack + if (c == left) + brak++; + else if (c == right) + brak--; + else if (!c) { + p--; + conoutf(@"missing \"%c\"", right); + return NULL; + } + } + char *s = newstring(word, p - word - 1); + if (left == '(') { + string t; + itoa(t, + execute( + s)); // evaluate () exps directly, and substitute result + s = exchangestr(s, t); + } + return s; +} + +char * +parseword(char *&p) // parse single argument, including expressions +{ + p += strspn(p, " \t\r"); + if (p[0] == '/' && p[1] == '/') + p += strcspn(p, "\n\0"); + if (*p == '\"') { + p++; + char *word = p; + p += strcspn(p, "\"\r\n\0"); + char *s = newstring(word, p - word); + if (*p == '\"') + p++; + return s; + } + if (*p == '(') + return parseexp(p, ')'); + if (*p == '[') + return parseexp(p, ']'); + char *word = p; + p += strcspn(p, "; \t\r\n\0"); + if (p - word == 0) + return NULL; + return newstring(word, p - word); +} + +char * +lookup(char *n) // find value of ident referenced with $ in exp +{ + @autoreleasepool { + __kindof Identifier *identifier = identifiers[@(n + 1)]; + + if ([identifier isKindOfClass:[Variable class]]) { + string t; + itoa(t, *[identifier storage]); + return exchangestr(n, t); + } else if ([identifier isKindOfClass:[Alias class]]) + return exchangestr(n, [identifier action].UTF8String); + } + + conoutf(@"unknown alias lookup: %s", n + 1); + return n; +} + +int +execute(char *p, bool isdown) // all evaluation happens here, recursively +{ + const int MAXWORDS = 25; // limit, remove + char *w[MAXWORDS]; + int val = 0; + for (bool cont = true; cont;) // for each ; seperated statement + { + int numargs = MAXWORDS; + loopi(MAXWORDS) // collect all argument values + { + w[i] = ""; + if (i > numargs) + continue; + char *s = parseword(p); // parse and evaluate exps + if (!s) { + numargs = i; + s = ""; + } + if (*s == '$') + s = lookup(s); // substitute variables + w[i] = s; + } + + p += strcspn(p, ";\n\0"); + cont = *p++ != + 0; // more statements if this isn't the end of the string + char *c = w[0]; + if (*c == '/') + c++; // strip irc-style command prefix + if (!*c) + continue; // empty statement + + @autoreleasepool { + __kindof Identifier *identifier = identifiers[@(c)]; + + if (identifier == nil) { + val = ATOI(c); + if (!val && *c != '0') + conoutf(@"unknown command: %s", c); + } else { + if ([identifier + isKindOfClass:[Command class]]) { + // game defined commands + // use very ad-hoc function signature, + // and just call it + val = [identifier + callWithArguments:w + numArguments:numargs + isDown:isdown]; + } else if ([identifier + isKindOfClass:[Variable + class]]) { + // game defined variables + if (isdown) { + if (!w[1][0]) + // var with no value + // just prints its + // current value + conoutf(@"%s = %d", c, + *[identifier + storage]); + else { + if ([identifier min] > + [identifier max]) { + conoutf( + @"variable " + @"is " + @"read-" + @"only"); + } else { + int i1 = + ATOI(w[1]); + if (i1 < + [identifier + min] || + i1 > + [identifier + max]) { + // clamp + // to + // valid + // range + i1 = + i1 < [identifier + min] + ? [identifier + min] + : [identifier + max]; + conoutf( + @"v" + @"a" + @"l" + @"i" + @"d" + @" " + @"r" + @"a" + @"n" + @"g" + @"e" + @" " + @"f" + @"o" + @"r" + @" " + @"%" + @"s" + @" " + @"i" + @"s" + @" " + @"%" + @"d" + @"." + @"." + @"%" + @"d", + c, + [identifier + min], + [identifier + max]); + } + *[identifier + storage] = + i1; + } + if ([identifier + function] != + NULL) + // call trigger + // function if + // available + ((void(__cdecl + *)())[identifier + function])(); + } + } + } else if ([identifier + isKindOfClass:[Alias class]]) { + // alias, also used as functions and + // (global) variables + for (int i = 1; i < numargs; i++) { + @autoreleasepool { + // set any arguments as + // (global) arg values + // so functions can + // access them + OFString *t = [OFString + stringWithFormat: + @"arg%d", i]; + alias(t, @(w[i])); + } + } + // create new string here because alias + // could rebind itself + char *action = newstring( + [identifier action].UTF8String); + val = execute(action, isdown); + gp()->deallocstr(action); + break; + } + } + } + loopj(numargs) gp()->deallocstr(w[j]); + } + + return val; +} + +// tab-completion of all identifiers + +int completesize = 0, completeidx = 0; + +void +resetcomplete() +{ + completesize = 0; +} + +void +complete(OFString *s_) +{ + @autoreleasepool { + std::unique_ptr copy(strdup(s_.UTF8String)); + char *s = copy.get(); + + if (*s != '/') { + string t; + strcpy_s(t, s); + strcpy_s(s, "/"); + strcat_s(s, t); + } + + if (!s[1]) + return; + + if (!completesize) { + completesize = strlen(s) - 1; + completeidx = 0; + } + + __block int idx = 0; + [identifiers enumerateKeysAndObjectsUsingBlock:^( + OFString *name, Identifier *identifier, bool *stop) { + if (strncmp(identifier.name.UTF8String, s + 1, + completesize) == 0 && + idx++ == completeidx) { + strcpy_s(s, "/"); + strcat_s(s, identifier.name.UTF8String); + } + }]; + + completeidx++; + + if (completeidx >= idx) + completeidx = 0; + } +} + +bool +execfile(OFString *cfgfile) +{ + @autoreleasepool { + OFMutableData *data; + @try { + data = [OFMutableData dataWithContentsOfFile:cfgfile]; + } @catch (id e) { + return false; + } + + // Ensure \0 termination. + [data addItem:""]; + + execute((char *)data.mutableItems); + return true; + } +} + +void +exec(OFString *cfgfile) +{ + if (!execfile(cfgfile)) { + @autoreleasepool { + conoutf(@"could not read \"%@\"", cfgfile); + } + } +} + +void +writecfg() +{ + OFStream *stream; + @try { + OFIRI *IRI = [Cube.sharedInstance.userDataIRI + IRIByAppendingPathComponent:@"config.cfg"]; + stream = [[OFIRIHandler handlerForIRI:IRI] openItemAtIRI:IRI + mode:@"w"]; + } @catch (id e) { + return; + } + + [stream writeString: + @"// automatically written on exit, do not modify\n" + @"// delete this file to have defaults.cfg overwrite these " + @"settings\n" + @"// modify settings in game, or put settings in " + @"autoexec.cfg to override anything\n" + @"\n"]; + writeclientinfo(stream); + [stream writeString:@"\n"]; + + [identifiers enumerateKeysAndObjectsUsingBlock:^( + OFString *name, __kindof Identifier *identifier, bool *stop) { + if (![identifier isKindOfClass:[Variable class]] || + ![identifier persisted]) + return; + + [stream writeFormat:@"%@ %d\n", identifier.name, + *[identifier storage]]; + }]; + [stream writeString:@"\n"]; + + writebinds(stream); + [stream writeString:@"\n"]; + + [identifiers enumerateKeysAndObjectsUsingBlock:^( + OFString *name, __kindof Identifier *identifier, bool *stop) { + if (![identifier isKindOfClass:[Alias class]] || + [identifier.name hasPrefix:@"nextmap_"]) + return; + + [stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name, + [identifier action]]; + }]; + + [stream close]; +} + +COMMAND(writecfg, ARG_NONE) + +// below the commands that implement a small imperative language. thanks to the +// semantics of +// () and [] expressions, any control construct can be defined trivially. + +void +intset(OFString *name, int v) +{ + @autoreleasepool { + alias(name, [OFString stringWithFormat:@"%d", v]); + } +} + +void +ifthen(OFString *cond, OFString *thenp, OFString *elsep) +{ + @autoreleasepool { + std::unique_ptr cmd(strdup( + (cond.UTF8String[0] != '0' ? thenp : elsep).UTF8String)); + + execute(cmd.get()); + } +} + +void +loopa(OFString *times, OFString *body_) +{ + @autoreleasepool { + int t = (int)times.longLongValue; + std::unique_ptr body(strdup(body_.UTF8String)); + + loopi(t) + { + intset(@"i", i); + execute(body.get()); + } + } +} + +void +whilea(OFString *cond_, OFString *body_) +{ + @autoreleasepool { + std::unique_ptr cond(strdup(cond_.UTF8String)); + std::unique_ptr body(strdup(body_.UTF8String)); + + while (execute(cond.get())) + execute(body.get()); + } +} + +void +onrelease(bool on, char *body) +{ + if (!on) + execute(body); +} + +void +concat(char *s) +{ + @autoreleasepool { + alias(@"s", @(s)); + } +} + +void +concatword(char *s) +{ + for (char *a = s, *b = s; *a = *b; b++) + if (*a != ' ') + a++; + concat(s); +} + +int +listlen(char *a) +{ + if (!*a) + return 0; + int n = 0; + while (*a) + if (*a++ == ' ') + n++; + return n + 1; +} + +void +at(OFString *s_, OFString *pos) +{ + @autoreleasepool { + int n = (int)pos.longLongValue; + std::unique_ptr copy(strdup(s_.UTF8String)); + char *s = copy.get(); + + loopi(n) s += strspn(s += strcspn(s, " \0"), " "); + s[strcspn(s, " \0")] = 0; + concat(s); + } +} + +COMMANDN(loop, loopa, ARG_2STR) +COMMANDN(while, whilea, ARG_2STR) +COMMANDN(if, ifthen, ARG_3STR) +COMMAND(onrelease, ARG_DWN1) +COMMAND(exec, ARG_1STR) +COMMAND(concat, ARG_VARI) +COMMAND(concatword, ARG_VARI) +COMMAND(at, ARG_2STR) +COMMAND(listlen, ARG_1EST) + +int +add(int a, int b) +{ + return a + b; +} +COMMANDN(+, add, ARG_2EXP) + +int +mul(int a, int b) +{ + return a * b; +} +COMMANDN(*, mul, ARG_2EXP) + +int +sub(int a, int b) +{ + return a - b; +} +COMMANDN(-, sub, ARG_2EXP) + +int +divi(int a, int b) +{ + return b ? a / b : 0; +} +COMMANDN(div, divi, ARG_2EXP) + +int +mod(int a, int b) +{ + return b ? a % b : 0; +} +COMMAND(mod, ARG_2EXP) + +int +equal(int a, int b) +{ + return (int)(a == b); +} +COMMANDN(=, equal, ARG_2EXP) + +int +lt(int a, int b) +{ + return (int)(a < b); +} +COMMANDN(<, lt, ARG_2EXP) + +int +gt(int a, int b) +{ + return (int)(a > b); +} +COMMANDN(>, gt, ARG_2EXP) + +int +strcmpa(char *a, char *b) +{ + return strcmp(a, b) == 0; +} +COMMANDN(strcmp, strcmpa, ARG_2EST) + +int +rndn(int a) +{ + return a > 0 ? rnd(a) : 0; +} +COMMANDN(rnd, rndn, ARG_1EXP) + +int +explastmillis() +{ + return lastmillis; +} +COMMANDN(millis, explastmillis, ARG_1EXP) Index: src/meson.build ================================================================== --- src/meson.build +++ src/meson.build @@ -1,19 +1,22 @@ executable('client', [ + 'Alias.m', + 'Command.mm', 'Cube.mm', - 'Ident.m', + 'Identifier.m', 'KeyMapping.m', 'MD2.mm', 'MapModelInfo.m', 'Menu.m', 'MenuItem.m', + 'Variable.m', 'client.mm', 'clientextras.mm', 'clientgame.mm', 'clients2c.mm', - 'command.mm', + 'commands.mm', 'console.mm', 'editing.mm', 'entities.mm', 'init.mm', 'menus.mm',