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

View file

@ -1,17 +1,20 @@
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',