Clean up identifiers

FossilOrigin-Name: d35fd65699e79d96fc0588fff075f73b0dd7eb6ebfa0f6cffd0b0a3940e5fecd
This commit is contained in:
Jonathan Schleifer 2025-03-07 21:02:39 +00:00
parent 570b9e3bb3
commit c690c2b9ed
12 changed files with 364 additions and 276 deletions

15
src/Alias.h Normal file
View file

@ -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

15
src/Alias.m Normal file
View file

@ -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

18
src/Command.h Normal file
View file

@ -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

131
src/Command.mm Normal file
View file

@ -0,0 +1,131 @@
#import "Command.h"
#include <cube.h>
@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

View file

@ -1,18 +0,0 @@
#import <ObjFW/ObjFW.h>
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

View file

@ -1,4 +0,0 @@
#import "Ident.h"
@implementation Ident
@end

12
src/Identifier.h Normal file
View file

@ -0,0 +1,12 @@
#import <ObjFW/ObjFW.h>
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

12
src/Identifier.m Normal file
View file

@ -0,0 +1,12 @@
#import "Identifier.h"
@implementation Identifier
- (instancetype)initWithName:(OFString *)name
{
self = [super init];
_name = [name copy];
return self;
}
@end

20
src/Variable.h Normal file
View file

@ -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

21
src/Variable.m Normal file
View file

@ -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

View file

@ -5,7 +5,10 @@
#include <memory> #include <memory>
#import "Ident.h" #import "Alias.h"
#import "Command.h"
#import "Identifier.h"
#import "Variable.h"
void void
itoa(char *s, int i) itoa(char *s, int i)
@ -21,24 +24,25 @@ exchangestr(char *o, const char *n)
} }
// contains ALL vars/commands/aliases // contains ALL vars/commands/aliases
OFMutableDictionary<OFString *, Ident *> *idents; static OFMutableDictionary<OFString *, __kindof Identifier *> *identifiers;
void void
alias(OFString *name, OFString *action) alias(OFString *name, OFString *action)
{ {
Ident *b = idents[name]; Alias *alias = identifiers[name];
if (b == nil) { if (alias == nil) {
Ident *b = [[Ident alloc] init]; alias = [[Alias alloc] initWithName:name
b.type = ID_ALIAS; action:action
b.name = name; persisted:true];
b.action = action;
b.persist = true;
idents[b.name] = b; if (identifiers == nil)
identifiers = [[OFMutableDictionary alloc] init];
identifiers[name] = alias;
} else { } else {
if (b.type == ID_ALIAS) if ([alias isKindOfClass:[Alias class]])
b.action = action; alias.action = action;
else else
conoutf( conoutf(
@"cannot redefine builtin %@ with an alias", name); @"cannot redefine builtin %@ with an alias", name);
@ -47,22 +51,20 @@ alias(OFString *name, OFString *action)
COMMAND(alias, ARG_2STR) COMMAND(alias, ARG_2STR)
int int
variable(OFString *name, int min, int cur, int max, int *storage, void (*fun)(), variable(OFString *name, int min, int cur, int max, int *storage,
bool persist) void (*function)(), bool persisted)
{ {
if (idents == nil) Variable *variable = [[Variable alloc] initWithName:name
idents = [[OFMutableDictionary alloc] init]; min:min
max:max
storage:storage
function:function
persisted:persisted];
Ident *v = [[Ident alloc] init]; if (identifiers == nil)
v.type = ID_VAR; identifiers = [[OFMutableDictionary alloc] init];
v.name = name;
v.min = min;
v.max = max;
v.storage = storage;
v.fun = fun;
v.persist = persist;
idents[name] = v; identifiers[name] = variable;
return cur; return cur;
} }
@ -70,43 +72,43 @@ variable(OFString *name, int min, int cur, int max, int *storage, void (*fun)(),
void void
setvar(OFString *name, int i) setvar(OFString *name, int i)
{ {
*idents[name].storage = i; *[identifiers[name] storage] = i;
} }
int int
getvar(OFString *name) getvar(OFString *name)
{ {
return *idents[name].storage; return *[identifiers[name] storage];
} }
bool bool
identexists(OFString *name) identexists(OFString *name)
{ {
return (idents[name] != nil); return (identifiers[name] != nil);
} }
OFString * OFString *
getalias(OFString *name) getalias(OFString *name)
{ {
Ident *i = idents[name]; Alias *alias = identifiers[name];
return i != nil && i.type == ID_ALIAS ? i.action : nil;
if ([alias isKindOfClass:[Alias class]])
return alias.action;
return nil;
} }
bool bool
addcommand(OFString *name, void (*fun)(), int narg) addcommand(OFString *name, void (*function)(), int argumentsTypes)
{ {
if (idents == nil) Command *command = [[Command alloc] initWithName:name
idents = [[OFMutableDictionary alloc] init]; function:function
argumentsTypes:argumentsTypes];
@autoreleasepool { if (identifiers == nil)
Ident *c = [[Ident alloc] init]; identifiers = [[OFMutableDictionary alloc] init];
c.type = ID_COMMAND;
c.name = name;
c.fun = fun;
c.narg = narg;
idents[name] = c; identifiers[name] = command;
}
return false; return false;
} }
@ -171,18 +173,14 @@ char *
lookup(char *n) // find value of ident referenced with $ in exp lookup(char *n) // find value of ident referenced with $ in exp
{ {
@autoreleasepool { @autoreleasepool {
Ident *ID = idents[@(n + 1)]; __kindof Identifier *identifier = identifiers[@(n + 1)];
if (ID != nil) { if ([identifier isKindOfClass:[Variable class]]) {
switch (ID.type) { string t;
case ID_VAR: itoa(t, *[identifier storage]);
string t; return exchangestr(n, t);
itoa(t, *(ID.storage)); } else if ([identifier isKindOfClass:[Alias class]])
return exchangestr(n, t); return exchangestr(n, [identifier action].UTF8String);
case ID_ALIAS:
return exchangestr(n, ID.action.UTF8String);
}
}
} }
conoutf(@"unknown alias lookup: %s", n + 1); conoutf(@"unknown alias lookup: %s", n + 1);
@ -223,185 +221,37 @@ execute(char *p, bool isdown) // all evaluation happens here, recursively
continue; // empty statement continue; // empty statement
@autoreleasepool { @autoreleasepool {
Ident *ID = idents[@(c)]; __kindof Identifier *identifier = identifiers[@(c)];
if (ID == nil) { if (identifier == nil) {
val = ATOI(c); val = ATOI(c);
if (!val && *c != '0') if (!val && *c != '0')
conoutf(@"unknown command: %s", c); conoutf(@"unknown command: %s", c);
} else { } else {
switch (ID.type) { if ([identifier
// game defined commands isKindOfClass:[Command class]]) {
case ID_COMMAND: // game defined commands
// use very ad-hoc function signature, // use very ad-hoc function signature,
// and just call it // and just call it
switch (ID.narg) { val = [identifier
case ARG_1INT: callWithArguments:w
if (isdown) numArguments:numargs
((void(__cdecl *)( isDown:isdown];
int))ID.fun)( } else if ([identifier
ATOI(w[1])); isKindOfClass:[Variable
break; class]]) {
case ARG_2INT: // game defined variables
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 (isdown) {
if (!w[1][0]) if (!w[1][0])
// var with no value // var with no value
// just prints its // just prints its
// current value // current value
conoutf(@"%s = %d", c, conoutf(@"%s = %d", c,
*ID.storage); *[identifier
storage]);
else { else {
if (ID.min > ID.max) { if ([identifier min] >
[identifier max]) {
conoutf( conoutf(
@"variable " @"variable "
@"is " @"is "
@ -411,17 +261,22 @@ execute(char *p, bool isdown) // all evaluation happens here, recursively
int i1 = int i1 =
ATOI(w[1]); ATOI(w[1]);
if (i1 < if (i1 <
ID.min || [identifier
min] ||
i1 > i1 >
ID.max) { [identifier
max]) {
// clamp // clamp
// to // to
// valid // valid
// range // range
i1 = i1 =
i1 < ID.min i1 < [identifier
? ID.min min]
: ID.max; ? [identifier
min]
: [identifier
max];
conoutf( conoutf(
@"v" @"v"
@"a" @"a"
@ -452,26 +307,30 @@ execute(char *p, bool isdown) // all evaluation happens here, recursively
@"%" @"%"
@"d", @"d",
c, c,
ID.min, [identifier
ID.max); min],
[identifier
max]);
} }
*ID.storage = *[identifier
storage] =
i1; i1;
} }
if (ID.fun) if ([identifier
function] !=
NULL)
// call trigger // call trigger
// function if // function if
// available // available
((void(__cdecl ((void(__cdecl
*)())ID *)())[identifier
.fun)(); function])();
} }
} }
break; } else if ([identifier
isKindOfClass:[Alias class]]) {
// alias, also used as functions and (global) // alias, also used as functions and
// variables // (global) variables
case ID_ALIAS:
for (int i = 1; i < numargs; i++) { for (int i = 1; i < numargs; i++) {
@autoreleasepool { @autoreleasepool {
// set any arguments as // set any arguments as
@ -486,8 +345,8 @@ execute(char *p, bool isdown) // all evaluation happens here, recursively
} }
// create new string here because alias // create new string here because alias
// could rebind itself // could rebind itself
char *action = char *action = newstring(
newstring(ID.action.UTF8String); [identifier action].UTF8String);
val = execute(action, isdown); val = execute(action, isdown);
gp()->deallocstr(action); gp()->deallocstr(action);
break; break;
@ -500,7 +359,7 @@ execute(char *p, bool isdown) // all evaluation happens here, recursively
return val; return val;
} }
// tab-completion of all idents // tab-completion of all identifiers
int completesize = 0, completeidx = 0; int completesize = 0, completeidx = 0;
@ -533,13 +392,13 @@ complete(OFString *s_)
} }
__block int idx = 0; __block int idx = 0;
[idents enumerateKeysAndObjectsUsingBlock:^( [identifiers enumerateKeysAndObjectsUsingBlock:^(
OFString *name, Ident *ident, bool *stop) { OFString *name, Identifier *identifier, bool *stop) {
if (strncmp(ident.name.UTF8String, s + 1, if (strncmp(identifier.name.UTF8String, s + 1,
completesize) == 0 && completesize) == 0 &&
idx++ == completeidx) { idx++ == completeidx) {
strcpy_s(s, "/"); strcpy_s(s, "/");
strcat_s(s, ident.name.UTF8String); strcat_s(s, identifier.name.UTF8String);
} }
}]; }];
@ -602,24 +461,28 @@ writecfg()
writeclientinfo(stream); writeclientinfo(stream);
[stream writeString:@"\n"]; [stream writeString:@"\n"];
[idents enumerateKeysAndObjectsUsingBlock:^( [identifiers enumerateKeysAndObjectsUsingBlock:^(
OFString *name, Ident *ident, bool *stop) { OFString *name, __kindof Identifier *identifier, bool *stop) {
if (ident.type == ID_VAR && ident.persist) { if (![identifier isKindOfClass:[Variable class]] ||
[stream ![identifier persisted])
writeFormat:@"%@ %d\n", ident.name, *ident.storage]; return;
}
[stream writeFormat:@"%@ %d\n", identifier.name,
*[identifier storage]];
}]; }];
[stream writeString:@"\n"]; [stream writeString:@"\n"];
writebinds(stream); writebinds(stream);
[stream writeString:@"\n"]; [stream writeString:@"\n"];
[idents enumerateKeysAndObjectsUsingBlock:^( [identifiers enumerateKeysAndObjectsUsingBlock:^(
OFString *name, Ident *ident, bool *stop) { OFString *name, __kindof Identifier *identifier, bool *stop) {
if (ident.type == ID_ALIAS && if (![identifier isKindOfClass:[Alias class]] ||
![ident.name hasPrefix:@"nextmap_"]) [identifier.name hasPrefix:@"nextmap_"])
[stream writeFormat:@"alias \"%@\" [%@]\n", ident.name, return;
ident.action];
[stream writeFormat:@"alias \"%@\" [%@]\n", identifier.name,
[identifier action]];
}]; }];
[stream close]; [stream close];

View file

@ -1,17 +1,20 @@
executable('client', executable('client',
[ [
'Alias.m',
'Command.mm',
'Cube.mm', 'Cube.mm',
'Ident.m', 'Identifier.m',
'KeyMapping.m', 'KeyMapping.m',
'MD2.mm', 'MD2.mm',
'MapModelInfo.m', 'MapModelInfo.m',
'Menu.m', 'Menu.m',
'MenuItem.m', 'MenuItem.m',
'Variable.m',
'client.mm', 'client.mm',
'clientextras.mm', 'clientextras.mm',
'clientgame.mm', 'clientgame.mm',
'clients2c.mm', 'clients2c.mm',
'command.mm', 'commands.mm',
'console.mm', 'console.mm',
'editing.mm', 'editing.mm',
'entities.mm', 'entities.mm',