aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-11-23 15:57:44 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-11-23 15:57:44 -0500
commit83a2bff4bb04b0189d17419baf8ca520992d5033 (patch)
tree63c24b6419e0896d985cb0c9f30b742274e95833
parent2a24b0a3fc3c4986572ae2c4ea0e8e387497a7f6 (diff)
Added Metadata section for files instead of _HELP and _USAGE
-rw-r--r--CHANGES.md7
-rw-r--r--docs/command-line-parsing.md33
-rw-r--r--src/ast.c17
-rw-r--r--src/ast.h5
-rw-r--r--src/compile/cli.c142
-rw-r--r--src/compile/cli.h3
-rw-r--r--src/compile/files.c1
-rw-r--r--src/compile/statements.c1
-rw-r--r--src/parse/expressions.c2
-rw-r--r--src/parse/files.c41
-rw-r--r--src/parse/text.c12
-rw-r--r--src/parse/text.h4
-rw-r--r--src/tomo.c6
-rw-r--r--src/typecheck.c7
14 files changed, 196 insertions, 85 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 42db4715..f850d535 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -12,6 +12,13 @@
- Syntax for text literals and inline C code has been simplified somewhat.
- Syntax for tables has changed to use colons (`{k: v}`) instead of equals
(`{k=v}`).
+- Added metadata format instead of `_HELP`/`_USAGE`:
+ ```
+ HELP: "Help text"
+ USAGE: "Usage text"
+ MANPAGE_SYNOPSYS: "Synopsys..."
+ MANPAGE_DESCRIPTION: (./description.txt)
+ ```
- Added `Path.lines()`.
- Added `Text.find(text, target, start=1)`.
- Added `at_cleanup()` to register cleanup functions.
diff --git a/docs/command-line-parsing.md b/docs/command-line-parsing.md
index 714e6e9c..92e9a1c9 100644
--- a/docs/command-line-parsing.md
+++ b/docs/command-line-parsing.md
@@ -223,3 +223,36 @@ OPTIONS
--frob | --no-frob
Whether or not to frob your gropnoggles
```
+
+## Metadata
+
+You can specify metadata for a program, which is used for CLI messages like
+`--help`, as well as manpage documentation. Metadata can be specified as either
+a text literal (no interpolation) or as a file path literal.
+
+```
+USAGE: "--foo <n>"
+HELP: "
+ This is some custom help text.
+ You can use these flags:
+
+ --foo <n> The foo parameter
+ --help Show this message
+"
+MANPAGE_DESCRIPTION: (./description.roff)
+```
+
+Supported metadata:
+
+- `USAGE`: the short form usage shown in CLI parsing errors and help pages. This
+ should be a single line without the name of the program, so `USAGE: "--foo"`
+ would translate to the error message `Usage: myprogram --foo`. If this is not
+ present, it will be generated automatically.
+
+- `HELP`: The help message displayed when the `--help` flag is used or when there
+ is an argument parsing error. This should be a description of the program with
+ a multi-line documentation of commonly used flags.
+
+- `MANPAGE_SYNOPSYS`: the synopsis section of the manpage (inserted literally).
+
+- `MANPAGE_DESCRIPTION`: the description section of the manpage (inserted literally).
diff --git a/src/ast.c b/src/ast.c
index 432ce2d4..b2730d21 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -274,6 +274,8 @@ Text_t ast_to_sexp(ast_t *ast) {
T(Assert, "(Assert ", ast_to_sexp(data.expr), " ", optional_sexp("message", data.message), ")");
T(Use, "(Use ", optional_sexp("var", data.var), " ", quoted_text(data.path), ")");
T(InlineCCode, "(InlineCCode ", ast_list_to_sexp(data.chunks), optional_type_sexp("type", data.type_ast), ")");
+ T(Metadata, "((Metadata ", Text$quoted(data.key, false, Text("\"")), " ",
+ Text$quoted(data.value, false, Text("\"")), ")");
default: errx(1, "S-expressions are not implemented for this AST");
#undef T
}
@@ -463,7 +465,8 @@ void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata) {
case Int:
case Num:
case Path:
- case TextLiteral: return;
+ case TextLiteral:
+ case Metadata: return;
case TextJoin: ast_visit_list(Match(ast, TextJoin)->children, visitor, userdata); return;
case Declare: {
DeclareMatch(decl, ast, Declare);
@@ -780,3 +783,15 @@ void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *use
Closure_t fn = {.fn = visitor, .userdata = userdata};
ast_visit(ast, _type_ast_visit, &fn);
}
+
+OptionalText_t ast_metadata(ast_t *ast, const char *key) {
+ if (ast->tag != Block) return NONE_TEXT;
+ Text_t key_text = Text$from_str(key);
+ for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
+ if (stmt->ast->tag == Metadata) {
+ DeclareMatch(m, stmt->ast, Metadata);
+ if (Text$equal_values(m->key, key_text)) return m->value;
+ }
+ }
+ return NONE_TEXT;
+}
diff --git a/src/ast.h b/src/ast.h
index c01ba68a..7fa9092a 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -276,6 +276,7 @@ typedef enum {
Use,
InlineCCode,
ExplicitlyTyped,
+ Metadata,
} ast_e;
#define NUM_AST_TAGS (ExplicitlyTyped + 1)
@@ -458,6 +459,9 @@ struct ast_s {
ast_t *ast;
struct type_s *type;
} ExplicitlyTyped;
+ struct {
+ Text_t key, value;
+ } Metadata;
} __data;
};
@@ -483,3 +487,4 @@ CONSTFUNC ast_e binop_tag(ast_e tag);
CONSTFUNC bool is_binary_operation(ast_t *ast);
void ast_visit(ast_t *ast, void (*visitor)(ast_t *, void *), void *userdata);
void type_ast_visit(ast_t *ast, void (*visitor)(type_ast_t *, void *), void *userdata);
+OptionalText_t ast_metadata(ast_t *ast, const char *key);
diff --git a/src/compile/cli.c b/src/compile/cli.c
index e5756521..b92a5784 100644
--- a/src/compile/cli.c
+++ b/src/compile/cli.c
@@ -58,80 +58,86 @@ static OptionalText_t flagify(const char *name, bool prefix) {
return flag;
}
-public
-Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version) {
+static Text_t generate_usage(env_t *env, type_t *fn_type) {
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;
- }
+ 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(" [\x1b[1m--help\x1b[m]");
- for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
- usage = Texts(usage, " ");
- type_t *t = get_arg_type(main_env, arg);
- OptionalText_t flag = flagify(arg->name, arg->default_val != NULL);
- assert(flag.tag != TEXT_NONE);
- OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL);
- Text_t flags = Texts("\x1b[1m", flag, "\x1b[m");
- if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m");
+ }
+ env_t *main_env = fresh_scope(env);
+ Text_t usage = explicit_help_flag ? EMPTY_TEXT : Text(" [\x1b[1m--help\x1b[m]");
+ for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
+ usage = Texts(usage, " ");
+ type_t *t = get_arg_type(main_env, arg);
+ OptionalText_t flag = flagify(arg->name, arg->default_val != NULL);
+ assert(flag.tag != TEXT_NONE);
+ OptionalText_t alias_flag = flagify(arg->alias, arg->default_val != NULL);
+ Text_t flags = Texts("\x1b[1m", flag, "\x1b[m");
+ if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[1m", alias_flag, "\x1b[m");
+ if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
+ flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
+ if (arg->default_val || value_type(t)->tag == BoolType) {
if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- flags = Texts(flags, "|\x1b[1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
- if (arg->default_val || value_type(t)->tag == BoolType) {
- 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, Text("|")), "]");
- else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
- } else {
- usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m");
- }
+ usage = Texts(usage, "[", flags, "]");
+ else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
+ else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]");
+ } else {
+ usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m");
}
- code = Texts(code,
- "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), "
- "Text$from_str(argv[0])",
- usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n");
}
- if (!help_binding) {
- Text_t help_text = fn_info->args ? Text("\n") : Text("\n\n\x1b[2;3m No arguments...\x1b[m");
-
- for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
- help_text = Texts(help_text, "\n");
- type_t *t = get_arg_type(main_env, arg);
- OptionalText_t flag = flagify(arg->name, true);
- assert(flag.tag != TEXT_NONE);
- OptionalText_t alias_flag = flagify(arg->alias, true);
- Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m");
- if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m");
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
- if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
- help_text = Texts(help_text, " ", flags);
- else
- help_text = Texts(help_text, " ", flags, " \x1b[1;34m",
- get_flag_options(t, Text("\x1b[m | \x1b[1;34m")), "\x1b[m");
-
- if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m");
- if (arg->default_val) {
- Text_t default_text =
- Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start));
- help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m");
- }
+ return usage;
+}
+
+static Text_t generate_help(env_t *env, type_t *fn_type) {
+ DeclareMatch(fn_info, fn_type, FunctionType);
+ env_t *main_env = fresh_scope(env);
+ Text_t help_text = EMPTY_TEXT;
+
+ for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
+ help_text = Texts(help_text, "\n");
+ type_t *t = get_arg_type(main_env, arg);
+ OptionalText_t flag = flagify(arg->name, true);
+ assert(flag.tag != TEXT_NONE);
+ OptionalText_t alias_flag = flagify(arg->alias, true);
+ Text_t flags = Texts("\x1b[33;1m", flag, "\x1b[m");
+ if (alias_flag.tag != TEXT_NONE) flags = Texts(flags, ",\x1b[33;1m", alias_flag, "\x1b[m");
+ if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
+ flags = Texts(flags, "|\x1b[33;1m--no-", Text$without_prefix(flag, Text("--")), "\x1b[m");
+ if (t->tag == BoolType || (t->tag == OptionalType && Match(t, OptionalType)->type->tag == BoolType))
+ help_text = Texts(help_text, " ", flags);
+ else
+ help_text = Texts(help_text, " ", flags, " \x1b[1;34m", get_flag_options(t, Text("\x1b[m | \x1b[1;34m")),
+ "\x1b[m");
+
+ if (arg->comment.length > 0) help_text = Texts(help_text, " \x1b[3m", arg->comment, "\x1b[m");
+ if (arg->default_val) {
+ Text_t default_text =
+ Text$from_strn(arg->default_val->start, (size_t)(arg->default_val->end - arg->default_val->start));
+ help_text = Texts(help_text, " \x1b[2m(default:", default_text, ")\x1b[m");
}
- code = Texts(code, "Text_t help = Texts(usage, ", quoted_text(Texts(help_text, "\n")), ");\n");
- help_code = Text("help");
}
+ return help_text;
+}
+
+public
+Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version) {
+ DeclareMatch(fn_info, fn_type, FunctionType);
+
+ Text_t code = EMPTY_TEXT;
+ OptionalText_t usage = ast_metadata(ast, "USAGE");
+ if (usage.tag == TEXT_NONE) usage = generate_usage(env, fn_type);
+
+ OptionalText_t help = ast_metadata(ast, "HELP");
+ if (help.tag == TEXT_NONE) help = generate_help(env, fn_type);
+
+ code = Texts(code,
+ "Text_t usage = Texts(Text(\"\\x1b[1mUsage:\\x1b[m \"), "
+ "Text$from_str(argv[0]), Text(\" \")",
+ usage.length == 0 ? EMPTY_TEXT : Texts(", Text(", quoted_text(usage), ")"), ");\n",
+ "Text_t help = Texts(usage, Text(\"\\n\\n\"", quoted_text(help), "));\n");
for (arg_t *arg = fn_info->args; arg; arg = arg->next) {
code = Texts(code, compile_declaration(arg->type, Texts("_$", Text$from_str(arg->name))), " = ",
@@ -151,7 +157,7 @@ Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const c
"},\n");
}
code = Texts(code, "};\n");
- code = Texts(code, "tomo_parse_args(argc, argv, ", usage_code, ", ", help_code, ", ", version_code,
+ code = Texts(code, "tomo_parse_args(argc, argv, usage, help, ", version_code,
", sizeof(cli_args)/sizeof(cli_args[0]), cli_args);\n");
// Lazily initialize default values to prevent side effects
diff --git a/src/compile/cli.h b/src/compile/cli.h
index fa60eccf..fbb7347b 100644
--- a/src/compile/cli.h
+++ b/src/compile/cli.h
@@ -2,9 +2,10 @@
#pragma once
+#include "../ast.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
#include "../types.h"
-Text_t compile_cli_arg_call(env_t *env, Text_t fn_name, type_t *fn_type, const char *version);
+Text_t compile_cli_arg_call(env_t *env, ast_t *ast, Text_t fn_name, type_t *fn_type, const char *version);
Text_t compile_manpage(Text_t program, OptionalText_t synopsis, OptionalText_t description, arg_t *args);
diff --git a/src/compile/files.c b/src/compile/files.c
index a4cc07fe..b916f23f 100644
--- a/src/compile/files.c
+++ b/src/compile/files.c
@@ -142,6 +142,7 @@ Text_t compile_top_level_code(env_t *env, ast_t *ast) {
}
return code;
}
+ case Metadata:
default: return EMPTY_TEXT;
}
}
diff --git a/src/compile/statements.c b/src/compile/statements.c
index 0cd85b5d..c6ceccd9 100644
--- a/src/compile/statements.c
+++ b/src/compile/statements.c
@@ -216,6 +216,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) {
return EMPTY_TEXT;
}
}
+ case Metadata: return EMPTY_TEXT;
default:
// print("Is discardable: ", ast_to_sexp_str(ast), " ==> ",
// is_discardable(env, ast));
diff --git a/src/parse/expressions.c b/src/parse/expressions.c
index b43e4f3a..d031c49f 100644
--- a/src/parse/expressions.c
+++ b/src/parse/expressions.c
@@ -168,7 +168,7 @@ ast_t *parse_term_no_suffix(parse_ctx_t *ctx, const char *pos) {
(void)(false || (term = parse_none(ctx, pos)) || (term = parse_num(ctx, pos)) // Must come before int
|| (term = parse_int(ctx, pos)) || (term = parse_negative(ctx, pos)) // Must come after num/int
|| (term = parse_heap_alloc(ctx, pos)) || (term = parse_stack_reference(ctx, pos))
- || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos)) || (term = parse_path(ctx, pos))
+ || (term = parse_bool(ctx, pos)) || (term = parse_text(ctx, pos, true)) || (term = parse_path(ctx, pos))
|| (term = parse_lambda(ctx, pos)) || (term = parse_parens(ctx, pos)) || (term = parse_table(ctx, pos))
|| (term = parse_var(ctx, pos)) || (term = parse_list(ctx, pos)) || (term = parse_reduction(ctx, pos))
|| (term = parse_pass(ctx, pos)) || (term = parse_defer(ctx, pos)) || (term = parse_skip(ctx, pos))
diff --git a/src/parse/files.c b/src/parse/files.c
index 23e940e9..f5d9554a 100644
--- a/src/parse/files.c
+++ b/src/parse/files.c
@@ -7,8 +7,11 @@
#include <string.h>
#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/paths.h"
#include "../stdlib/stdlib.h"
#include "../stdlib/tables.h"
+#include "../stdlib/text.h"
#include "../stdlib/util.h"
#include "context.h"
#include "errors.h"
@@ -31,6 +34,35 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) {
return declaration;
}
+static ast_t *parse_metadata(parse_ctx_t *ctx, const char *pos) {
+ const char *start = pos;
+ const char *key = get_id(&pos);
+ if (!key) return NULL;
+ spaces(&pos);
+ if (!match(&pos, ":")) return NULL;
+ spaces(&pos);
+ ast_t *value = parse_text(ctx, pos, false);
+ Text_t value_text = EMPTY_TEXT;
+ if (value) {
+ for (ast_list_t *child = Match(value, TextJoin)->children; child; child = child->next) {
+ if (child->ast->tag != TextLiteral)
+ parser_err(ctx, child->ast->start, child->ast->end, "Text interpolations are not allowed in metadata");
+ value_text = Texts(value_text, Match(child->ast, TextLiteral)->text);
+ }
+ } else {
+ value = parse_path(ctx, pos);
+ if (!value) return NULL;
+ Path_t path = Path$from_str(Match(value, Path)->path);
+ path = Path$resolved(path, Path$parent(Path$from_str(ctx->file->filename)));
+ OptionalText_t contents = Path$read(path);
+ if (contents.tag == TEXT_NONE) parser_err(ctx, value->start, value->end, "File not found: ", path);
+ value_text = Text$trim(contents, Text("\r\n\t "), true, true);
+ }
+ pos = value->end;
+
+ return NewAST(ctx->file, start, pos, Metadata, .key = Text$from_str(key), .value = value_text);
+}
+
ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
whitespace(ctx, &pos);
@@ -40,10 +72,11 @@ ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) {
whitespace(ctx, &next);
if (get_indent(ctx, next) != 0) break;
ast_t *stmt;
- if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def))
- || (stmt = optional(ctx, &pos, parse_enum_def)) || (stmt = optional(ctx, &pos, parse_lang_def))
- || (stmt = optional(ctx, &pos, parse_convert_def)) || (stmt = optional(ctx, &pos, parse_use))
- || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_top_declaration))) {
+ if ((stmt = optional(ctx, &pos, parse_metadata)) || (stmt = optional(ctx, &pos, parse_struct_def))
+ || (stmt = optional(ctx, &pos, parse_func_def)) || (stmt = optional(ctx, &pos, parse_enum_def))
+ || (stmt = optional(ctx, &pos, parse_lang_def)) || (stmt = optional(ctx, &pos, parse_convert_def))
+ || (stmt = optional(ctx, &pos, parse_use)) || (stmt = optional(ctx, &pos, parse_inline_c))
+ || (stmt = optional(ctx, &pos, parse_top_declaration))) {
statements = new (ast_list_t, .ast = stmt, .next = statements);
pos = stmt->end;
whitespace(ctx, &pos); // TODO: check for newline
diff --git a/src/parse/text.c b/src/parse/text.c
index 7650955c..e23b8417 100644
--- a/src/parse/text.c
+++ b/src/parse/text.c
@@ -17,7 +17,7 @@
#include "types.h"
#include "utils.h"
-static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) {
+static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, bool allow_interps) {
const char *pos = *out_pos;
int64_t starting_indent = get_indent(ctx, pos);
@@ -41,6 +41,8 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) {
parser_err(ctx, pos, pos, "I expected a valid text here");
}
+ if (!allow_interps) interp = NULL;
+
ast_list_t *chunks = NULL;
Text_t chunk = EMPTY_TEXT;
const char *chunk_start = pos;
@@ -123,9 +125,9 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) {
return chunks;
}
-ast_t *parse_text(parse_ctx_t *ctx, const char *pos) {
+ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps) {
// ('"' ... '"' / "'" ... "'" / "`" ... "`")
- // "$" [name] [interp-char] quote-char ... close-quote
+ // "$" [name] quote-char ... close-quote
const char *start = pos;
const char *lang = NULL;
@@ -136,7 +138,7 @@ ast_t *parse_text(parse_ctx_t *ctx, const char *pos) {
if (!(*pos == '"' || *pos == '\'' || *pos == '`')) return NULL;
- ast_list_t *chunks = _parse_text_helper(ctx, &pos);
+ ast_list_t *chunks = _parse_text_helper(ctx, &pos, allow_interps);
bool colorize = match(&pos, "~") && match_word(&pos, "colorized");
return NewAST(ctx->file, start, pos, TextJoin, .lang = lang, .children = chunks, .colorize = colorize);
}
@@ -157,7 +159,7 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) {
parser_err(ctx, pos, pos + 1,
"This is not a valid string quotation character. Valid characters are: \"'`|/;([{<");
- ast_list_t *chunks = _parse_text_helper(ctx, &pos);
+ ast_list_t *chunks = _parse_text_helper(ctx, &pos, true);
return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type);
}
diff --git a/src/parse/text.h b/src/parse/text.h
index 6ab3cab2..865bc5a4 100644
--- a/src/parse/text.h
+++ b/src/parse/text.h
@@ -1,9 +1,11 @@
// Logic for parsing text literals
#pragma once
+#include <stdbool.h>
+
#include "../ast.h"
#include "context.h"
-ast_t *parse_text(parse_ctx_t *ctx, const char *pos);
+ast_t *parse_text(parse_ctx_t *ctx, const char *pos, bool allow_interps);
ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos);
ast_t *parse_path(parse_ctx_t *ctx, const char *pos);
diff --git a/src/tomo.c b/src/tomo.c
index fab725db..a04ccf59 100644
--- a/src/tomo.c
+++ b/src/tomo.c
@@ -841,7 +841,7 @@ void transpile_code(env_t *base_env, Path_t path) {
namespace_name(module_env, module_env->namespace, Text("$initialize")),
"();\n"
"\n",
- compile_cli_arg_call(module_env, main_binding->code, main_binding->type, version),
+ compile_cli_arg_call(module_env, ast, main_binding->code, main_binding->type, version),
"return 0;\n"
"}\n"));
}
@@ -877,7 +877,9 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t
Path_t manpage_file = build_file(Path$with_extension(path, Text(".1"), true), "");
if (clean_build || !Path$is_file(manpage_file, true) || is_stale(manpage_file, path, true)) {
- Text_t manpage = compile_manpage(Path$base_name(exe_path), NONE_TEXT, NONE_TEXT,
+ OptionalText_t synopsys = ast_metadata(ast, "MANPAGE_SYNOPSYS");
+ OptionalText_t description = ast_metadata(ast, "MANPAGE_DESCRIPTION");
+ Text_t manpage = compile_manpage(Path$base_name(exe_path), synopsys, description,
Match(main_binding->type, FunctionType)->args);
if (!quiet) print("Wrote manpage:\t", Path$relative_to(manpage_file, Path$current_dir()));
Path$write(manpage_file, manpage, 0644);
diff --git a/src/typecheck.c b/src/typecheck.c
index 98fbf6da..4e1f5554 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -1511,6 +1511,7 @@ type_t *get_type(env_t *env, ast_t *ast) {
}
case Unknown: code_err(ast, "I can't figure out the type of: ", ast_to_sexp_str(ast));
case ExplicitlyTyped: return Match(ast, ExplicitlyTyped)->type;
+ case Metadata: return Type(VoidType);
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
@@ -1529,7 +1530,8 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) {
case StructDef:
case EnumDef:
case LangDef:
- case Use: return true;
+ case Use:
+ case Metadata: return true;
default: break;
}
type_t *t = get_type(env, ast);
@@ -1686,7 +1688,8 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) {
default: return is_constant(env, binop.lhs) && is_constant(env, binop.rhs);
}
}
- case Use: return true;
+ case Use:
+ case Metadata: return true;
case FunctionCall: return false;
case InlineCCode: return true;
default: return false;