aboutsummaryrefslogtreecommitdiff
path: root/src/compile/cli.c
blob: d93e5f56241f031f2a8d5f4fb6c9e5a2b8627467 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// This file defines how to compile CLI argument parsing

#include "../environment.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/optionals.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "../typecheck.h"
#include "../types.h"
#include "compilation.h"

static Text_t get_flag_options(type_t *t, const char *separator) {
    if (t->tag == BoolType) {
        return Text("yes|no");
    } else if (t->tag == EnumType) {
        Text_t options = EMPTY_TEXT;
        for (tag_t *tag = Match(t, EnumType)->tags; tag; tag = tag->next) {
            options = Texts(options, tag->name);
            if (tag->next) options = Texts(options, separator);
        }
        return options;
    } else if (t->tag == IntType || t->tag == NumType || t->tag == BigIntType) {
        return Text("N");
    } else {
        return Text("...");
    }
}

static OptionalText_t flagify(const char *name, bool prefix) {
    if (!name) return NONE_TEXT;
    Text_t flag = Text$from_str(name);
    flag = Text$replace(flag, Text("_"), Text("-"));
    if (prefix) flag = flag.length == 1 ? Texts("-", flag) : Texts("--", flag);
    return flag;
}

public
Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) {
    DeclareMatch(fn_info, fn_type, FunctionType);

    env_t *main_env = fresh_scope(env);

    Text_t code = EMPTY_TEXT;
    binding_t *usage_binding = get_binding(env, "_USAGE");
    Text_t usage_code = usage_binding ? usage_binding->code : Text("usage");
    binding_t *help_binding = get_binding(env, "_HELP");
    Text_t help_code = help_binding ? help_binding->code : usage_code;
    if (!usage_binding) {
        bool explicit_help_flag = false;
        for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
            if (streq(arg->name, "help")) {
                explicit_help_flag = true;
                break;
            }
        }

        Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [--help]");
        for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
            usage = Texts(usage, " ");
            type_t *t = get_arg_type(main_env, arg);
            if (arg->default_val || arg->type->tag == OptionalType) {
                OptionalText_t flag = flagify(arg->name, true);
                assert(flag.tag != TEXT_NONE);
                OptionalText_t alias_flag = flagify(arg->alias, true);
                Text_t flags = alias_flag.tag != TEXT_NONE ? Texts(flag, "|", alias_flag) : flag;
                if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
                    usage = Texts(usage, "[", flags, "]");
                else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, "|"), "]");
                else usage = Texts(usage, "[", flags, "=", get_flag_options(t, "|"), "]");
            } else {
                OptionalText_t flag = flagify(arg->name, false);
                assert(flag.tag != TEXT_NONE);
                OptionalText_t alias_flag = flagify(arg->alias, true);
                if (t->tag == BoolType)
                    usage = Texts(usage, "<--", flag, alias_flag.tag != TEXT_NONE ? Texts("|", alias_flag) : EMPTY_TEXT,
                                  "|--no-", flag, ">");
                else if (t->tag == EnumType) usage = Texts(usage, get_flag_options(t, "|"));
                else if (t->tag == ListType) usage = Texts(usage, "[", flag, "...]");
                else usage = Texts(usage, "<", flag, ">");
            }
        }
        code = Texts(code,
                     "Text_t usage = Texts(Text(\"Usage: \"), "
                     "Text$from_str(argv[0])",
                     usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n");
    }

    for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
        type_t *opt_type = arg->type->tag == OptionalType ? arg->type : Type(OptionalType, .type = arg->type);
        code = Texts(code, compile_declaration(opt_type, Texts("_$", Text$from_str(arg->name))));
        if (arg->default_val) {
            Text_t default_val =
                arg->type ? compile_to_type(env, arg->default_val, arg->type) : compile(env, arg->default_val);
            if (arg->type->tag != OptionalType) default_val = promote_to_optional(arg->type, default_val);
            code = Texts(code, " = ", default_val);
        } else {
            code = Texts(code, " = ", compile_none(arg->type));
        }
        code = Texts(code, ";\n");
    }

    Text_t version_code = quoted_str(version);
    code = Texts(code, "tomo_parse_args(argc, argv, ", usage_code, ", ", help_code, ", ", version_code);
    for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
        code = Texts(code, ",\n{", quoted_text(Text$replace(Text$from_str(arg->name), Text("_"), Text("-"))), ", ",
                     (arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ",
                     compile_type_info(arg->type), ", &", Texts("_$", Text$from_str(arg->name)), "}");
        if (arg->alias) {
            code = Texts(code, ",\n{", quoted_text(Text$replace(Text$from_str(arg->alias), Text("_"), Text("-"))), ", ",
                         (arg->default_val || arg->type->tag == OptionalType) ? "false" : "true", ", ",
                         compile_type_info(arg->type), ", &", Texts("_$", Text$from_str(arg->name)), "}");
        }
    }
    code = Texts(code, ");\n");

    code = Texts(code, fn_name, "(");
    for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
        Text_t arg_code = Texts("_$", arg->name);
        if (arg->type->tag != OptionalType) arg_code = optional_into_nonnone(arg->type, arg_code);

        code = Texts(code, arg_code);
        if (arg->next) code = Texts(code, ", ");
    }
    code = Texts(code, ");\n");
    return code;
}