Files
raddebugger/src/linker/lnk_cmd_line.c
T
Nikita Smith efe2a78019 type command-line option values
* pass argv inputs directly to the config command line
* define each option's value type in the switch table so comma splitting
  is applied only to list typed switches
2026-08-17 22:55:58 -07:00

430 lines
12 KiB
C

// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
internal String8List
lnk_arg_list_parse_windows_rules(Arena *arena, String8 string)
{
Temp scratch = scratch_begin(&arena, 1);
String8List list = {0};
U8 *ptr = string.str;
U8 *opl = string.str + string.size;
while (ptr < opl) {
// skip white space and new lines
while(ptr < opl) {
U64 size = (U64)(opl - ptr);
UnicodeDecode uni = utf8_decode(ptr, size);
if (uni.codepoint != ' ' && uni.codepoint != '\t' && uni.codepoint != '\n' && uni.codepoint != '\r') {
break;
}
ptr += uni.inc;
}
if (ptr >= opl || *ptr == '\0') {
break;
}
String8List token_builder = {0};
U8 *anchor = ptr;
while (ptr < opl) {
UnicodeDecode uni;
uni = utf8_decode(ptr, (U64)(opl-ptr));
if (uni.codepoint == '\0' || uni.codepoint == '\t' || uni.codepoint == '\n' || uni.codepoint == '\r' || uni.codepoint == ' ') {
break;
}
// handle string and strip quotes
if (uni.codepoint == '"') {
String8 text_before_quote = str8(anchor, (U64)(ptr - anchor));
str8_list_push(scratch.arena, &token_builder, text_before_quote);
// advance past starting quote
ptr += uni.inc;
anchor = ptr;
U8 *quote_end = ptr;
while (ptr < opl) {
uni = utf8_decode(ptr, (U64)(opl - ptr));
ptr += uni.inc;
// skip escape char
if (uni.codepoint == '\\') {
uni = utf8_decode(ptr, (U64)(opl - ptr));
ptr += uni.inc;
} else if (uni.codepoint == '"' || uni.codepoint == '\0') {
break; // found matching quote char
}
quote_end = ptr;
}
String8 text_inside_quotes = str8(anchor, (U64)(quote_end - anchor));
str8_list_push(scratch.arena, &token_builder, text_inside_quotes);
anchor = ptr;
} else {
ptr += uni.inc;
}
}
// push remaining text
String8 text = str8(anchor, (U64)(ptr - anchor));
str8_list_push(scratch.arena, &token_builder, text);
// push token
String8 token = str8_list_join(arena, &token_builder, NULL);
if (token.size) {
str8_list_push(arena, &list, token);
}
}
scratch_end(scratch);
return list;
}
internal void
lnk_cmd_line_push_option_node(LNK_CmdLine *cmd_line, LNK_CmdOption *opt)
{
SLLQueuePush(cmd_line->first_option, cmd_line->last_option, opt);
cmd_line->option_count += 1;
}
internal LNK_CmdOption *
lnk_cmd_line_push_option_string(Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8 value)
{
// fill out node
LNK_CmdOption *opt = push_array(arena, LNK_CmdOption, 1);
opt->string = string;
opt->value = value;
// push node
lnk_cmd_line_push_option_node(cmd_line, opt);
return opt;
}
internal LNK_CmdOption *
lnk_cmd_line_push_option(Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value)
{
return lnk_cmd_line_push_option_string(arena, cmd_line, str8_cstring(string), str8_cstring(value));
}
internal LNK_CmdOption *
lnk_cmd_line_push_option_if_not_present(Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value)
{
if (!lnk_cmd_line_has_option(*cmd_line, string)) {
return lnk_cmd_line_push_option(arena, cmd_line, string, value);
}
return 0;
}
internal void
lnk_cmd_line_concat_in_place(LNK_CmdLine *list, LNK_CmdLine *to_concat)
{
if (list->option_count > 0) {
if (to_concat->option_count > 0) {
list->option_count += to_concat->option_count;
list->last_option->next = to_concat->first_option;
list->last_option = to_concat->last_option;
}
str8_list_concat_in_place(&list->input_list, &to_concat->input_list);
str8_list_concat_in_place(&list->raw_cmd_line, &to_concat->raw_cmd_line);
} else {
*list = *to_concat;
}
MemoryZeroStruct(to_concat);
}
internal LNK_CmdLine
lnk_cmd_line_parse_windows_rules(Arena *arena, String8List arg_list)
{
Temp scratch = scratch_begin(&arena, 1);
LNK_CmdLine cmd_line = {0};
cmd_line.raw_cmd_line = str8_list_copy(arena, &arg_list);
for EachNode(arg_node, String8Node, arg_list.first) {
String8 arg = arg_node->string;
if (str8_starts_with(arg, str8_lit("/")) || str8_starts_with(arg, str8_lit("-"))) {
U64 param_start_pos = str8_find_needle(arg, 0, str8_lit(":"), 0);
String8 option_name = str8_chop(arg, arg.size - param_start_pos);
// remove '/' or '-' from option name
option_name = str8_skip(option_name, 1);
// skip ':'
String8 value_string = str8_skip(arg, param_start_pos + 1);
// push command
option_name = push_str8_copy(arena, option_name);
value_string = push_str8_copy(arena, value_string);
lnk_cmd_line_push_option_string(arena, &cmd_line, option_name, value_string);
} else if (str8_starts_with(arg, str8_lit("@"))) {
lnk_cmd_line_push_option_string(arena, &cmd_line, push_str8_copy(arena, arg), str8_zero());
} else {
str8_list_push(arena, &cmd_line.input_list, push_str8_copy(arena, arg));
}
}
scratch_end(scratch);
return cmd_line;
}
internal LNK_CmdLine
lnk_cmd_line_from_stringfv_windows_rules(Arena *arena, char *fmt, va_list args)
{
Temp scratch = scratch_begin(&arena, 1);
String8 string = push_str8fv(scratch.arena, fmt, args);
String8List arg_list = lnk_arg_list_parse_windows_rules(scratch.arena, string);
LNK_CmdLine result = lnk_cmd_line_parse_windows_rules(arena, arg_list);
scratch_end(scratch);
return result;
}
internal LNK_CmdLine
lnk_cmd_line_from_stringf_windows_rules(Arena *arena, char *fmt, ...)
{
va_list args;
va_start(args, fmt);
LNK_CmdLine result = lnk_cmd_line_from_stringfv_windows_rules(arena, fmt, args);
va_end(args);
return result;
}
internal LNK_CmdOption *
lnk_cmd_line_option_from_string(LNK_CmdLine cmd_line, String8 string)
{
LNK_CmdOption *opt;
for (opt = cmd_line.first_option; opt != NULL; opt = opt->next) {
if (str8_matchi(string, opt->string)) {
break;
}
}
return opt;
}
internal B32
lnk_cmd_line_has_option_string(LNK_CmdLine cmd_line, String8 string)
{
LNK_CmdOption *opt = lnk_cmd_line_option_from_string(cmd_line, string);
B32 has_option = (opt != 0);
return has_option;
}
internal B32
lnk_cmd_line_has_option(LNK_CmdLine cmd_line, char *string)
{
return lnk_cmd_line_has_option_string(cmd_line, str8_cstring(string));
}
internal String8List
lnk_data_from_cmd_line(Arena *arena, LNK_CmdLine cmd_line)
{
String8List result = {0};
for EachNode(opt, LNK_CmdOption, cmd_line.first_option) {
// separate commands
if (opt != cmd_line.first_option) {
str8_list_pushf(arena, &result, " ");
}
// push command line option
str8_list_pushf(arena, &result, "/%S%s%S", opt->string, opt->value.size ? ":" : "", opt->value);
}
return result;
}
internal String8
lnk_env_var_chain_separator_from_rule(LNK_EnvVarRule rule)
{
String8 result = {0};
switch (rule) {
case LNK_EnvVarRule_Batch: { result = str8_lit(";"); } break;
case LNK_EnvVarRule_Bash: { result = str8_lit(":"); } break;
default: break;
}
return result;
}
internal LNK_EnvVar *
lnk_env_var_push(Arena *arena, HashMap *env_vars, String8 key, String8 value, LNK_EnvVarRule rule)
{
LNK_EnvVar *env_var = lnk_env_var_from_map(env_vars, key);
if (env_var == 0) {
env_var = push_array(arena, LNK_EnvVar, 1);
env_var->raw_value = value;
env_var->rule = rule;
hash_map_push_path_raw(arena, env_vars, key, env_var);
}
return env_var;
}
internal LNK_EnvVar *
lnk_env_var_push_string(Arena *arena, HashMap *env_vars, String8 string, LNK_EnvVarRule rule)
{
LNK_EnvVar *result = 0;
string = str8_skip_chop_whitespace(string);
// string -> (key, value)
String8 key = {0};
String8 val = {0};
switch (rule) {
case LNK_EnvVarRule_Batch: {
if (string.size >= 2) {
if (str8_match_wildcard(string, str8_lit("\"*\""), 0)) {
string = str8_chop(str8_skip(string, 1), 1);
}
}
U64 sep_idx = str8_find_needle(string, 0, str8_lit("="), 0);
if (sep_idx < string.size) {
// extract key and value
key = str8_skip_chop_whitespace(str8_prefix(string, sep_idx));
val = str8_skip_chop_whitespace(str8_skip(string, sep_idx+1));
// strip quotes
if (str8_match_wildcard(val, str8_lit("\"*\""), 0) || // "*"
str8_match_wildcard(val, str8_lit("\'*\'"), 0)) { // '*'
val = str8_chop(str8_skip(val, 1), 1);
}
}
} break;
case LNK_EnvVarRule_Bash: {
U64 sep_idx = str8_find_needle(string, 0, str8_lit("="), 0);
if (sep_idx < string.size) {
// extract key and value
key = str8_skip_chop_whitespace(str8_prefix(string, sep_idx));
val = str8_skip_chop_whitespace(str8_skip(string, sep_idx+1));
// strip quotes
if (str8_match_wildcard(val, str8_lit("\"*\""), 0) || // "*"
str8_match_wildcard(val, str8_lit("\'*\'"), 0)) { // '*'
val = str8_chop(str8_skip(val, 1), 1);
}
}
} break;
default: break;
}
if (key.size > 0) {
result = lnk_env_var_push(arena, env_vars, key, val, rule);
}
return result;
}
internal LNK_EnvVar *
lnk_env_var_push_batch(Arena *arena, HashMap *env_vars, String8 string)
{
return lnk_env_var_push_string(arena, env_vars, string, LNK_EnvVarRule_Batch);
}
internal LNK_EnvVar *
lnk_env_var_push_bash(Arena *arena, HashMap *env_vars, String8 string)
{
return lnk_env_var_push_string(arena, env_vars, string, LNK_EnvVarRule_Bash);
}
internal LNK_EnvVar *
lnk_env_var_batchf(Arena *arena, HashMap *env_vars, char *fmt, ...)
{
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(arena, fmt, args);
va_end(args);
LNK_EnvVar *result = lnk_env_var_push_string(arena, env_vars, string, LNK_EnvVarRule_Batch);
return result;
}
internal LNK_EnvVar *
lnk_env_var_bashf(Arena *arena, HashMap *env_vars, char *fmt, ...)
{
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(arena, fmt, args);
va_end(args);
LNK_EnvVar *result = lnk_env_var_push_string(arena, env_vars, string, LNK_EnvVarRule_Bash);
return result;
}
internal LNK_EnvVar *
lnk_env_var_from_mapf(HashMap *env_vars, char *fmt, ...)
{
Temp scratch = scratch_begin(0,0);
va_list args;
va_start(args, fmt);
String8 key = push_str8fv(scratch.arena, fmt, args);
va_end(args);
LNK_EnvVar *result = hash_map_search_path_raw(env_vars, key);
scratch_end(scratch);
return result;
}
internal LNK_EnvVar *
lnk_env_var_from_map(HashMap *env_vars, String8 key)
{
return hash_map_search_path_raw(env_vars, key);
}
internal String8List
lnk_value_list_from_env_var(Arena *arena, LNK_EnvVar *env_var)
{
return str8_split_by_string_chars(arena, env_var->raw_value, lnk_env_var_chain_separator_from_rule(env_var->rule), 0);
}
internal B32
lnk_env_var_to_u64(HashMap *env_vars, LNK_EnvVar *env_var, U64 *value_out)
{
String8 value = str8_skip_chop_whitespace(env_var->raw_value);
if (str8_match_wildcard(value, str8_lit("\"*\""), 0) ||
str8_match_wildcard(value, str8_lit("\'*\'"), 0)) {
value = str8_chop(str8_skip(value, 1), 1);
}
return try_u64_from_str8_c_rules(value, value_out);
}
internal String8
lnk_expand_env_vars_windows(Arena *arena, HashMap *env_vars, String8 string)
{
Temp scratch = scratch_begin(&arena, 1);
String8List list = {0};
for (U64 i = 0; i < string.size; ) {
U64 open = str8_find_needle(string, i, str8_lit("%"), 0);
U64 close = str8_find_needle(string, open+1, str8_lit("%"), 0);
String8 text = str8_substr(string, rng_1u64(i, open));
str8_list_push(scratch.arena, &list, text);
i += text.size;
if (open < close) {
String8 env_var_name = str8_substr(string, rng_1u64(open+1, close));
LNK_EnvVar *env_var = lnk_env_var_from_map(env_vars, env_var_name);
if (env_var) {
str8_list_push(scratch.arena, &list, env_var->raw_value);
i = close+1;
} else {
str8_list_pushf(scratch.arena, &list, "%%%S", env_var_name);
i = close;
}
}
}
String8 result = str8_list_join(arena, &list, 0);
scratch_end(scratch);
return result;
}
internal HashMap
lnk_env_vars_from_process_info(Arena *arena, ProcessInfo *proc_info, LNK_EnvVarRule rule)
{
HashMap env_vars = {0};
for EachNode(node, String8Node, proc_info->environment.first) {
lnk_env_var_push_string(arena, &env_vars, str8_copy(arena, node->string), rule);
}
return env_vars;
}