diff --git a/examples/demo/highlight.js b/examples/demo/highlight.js
new file mode 100644
index 0000000..7651e3a
--- /dev/null
+++ b/examples/demo/highlight.js
@@ -0,0 +1,36 @@
+const hl = Ant.highlight;
+const render = hl.render;
+
+console.log(render('Red text and back to normal'));
+console.log(render('Green, Blue, Yellow'));
+
+console.log(render('Bright Red vs Normal Red'));
+
+console.log(render('Red background with text'));
+console.log(render('Blue bg with white text'));
+
+console.log(render('Bold text and dim text'));
+console.log(render('
text'));
+console.log(render('Italic or italic, strikethrough, inverted'));
+
+console.log(render('Bold red or bold blue'));
+console.log(render('Dim cyan and italic green'));
+
+console.log(render('<#ff8800>Orange hex color#ff8800>'));
+console.log(render('<#0f0>Bright green shorthand#0f0>'));
+console.log(render('Magenta background'));
+
+console.log(render('Number: 42, String: hello'));
+console.log(render('Hex: 0xff, Float: 3.14'));
+
+console.log(render('red back to normal immediately'));
+
+console.log(render('Literal <<: less than, >>: greater than'));
+console.log(render('Literal %%: percent sign'));
+
+const code = `\nconst x = 42;
+function greet(name) {
+ console.log(\`Hello, \${name}!\`);
+}`;
+
+console.log(hl(code));
diff --git a/examples/demo/kat.js b/examples/demo/kat.js
new file mode 100755
index 0000000..ed4b17c
--- /dev/null
+++ b/examples/demo/kat.js
@@ -0,0 +1,21 @@
+#!/usr/bin/env ant
+
+import fs from 'node:fs';
+import path from 'node:path';
+
+function exit(error) {
+ console.log(error);
+ process.exit(1);
+}
+
+const file = process.argv[2];
+if (!file) exit('usage: kat ');
+
+try {
+ const content = fs.readFileSync(file, 'utf8');
+ if (/\.(c|m)?(j|t)s$/.test(path.extname(file))) {
+ console.log(Ant.highlight(content));
+ } else console.log(content);
+} catch (err) {
+ exit(err.message);
+}
diff --git a/src/highlight/iter.c b/src/highlight/iter.c
index 0950d9a..404e4bb 100644
--- a/src/highlight/iter.c
+++ b/src/highlight/iter.c
@@ -356,6 +356,13 @@ bool hl_iter_next(hl_iter *it, hl_span *out) {
return true;
}
+ if (i == 0 && c == '#' && i + 1 < input_len && input[i + 1] == '!') {
+ while (i < input_len && input[i] != '\n') i++;
+ *out = (hl_span){ 0, i, HL_COMMENT };
+ it->pos = i;
+ return true;
+ }
+
if (it->state.mode == HL_STATE_STRING_SINGLE || it->state.mode == HL_STATE_STRING_DOUBLE) {
char quote = (it->state.mode == HL_STATE_STRING_SINGLE) ? '\'' : '"';
size_t start = i;
diff --git a/src/modules/builtin.c b/src/modules/builtin.c
index 6f557af..66b4737 100644
--- a/src/modules/builtin.c
+++ b/src/modules/builtin.c
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#ifdef __APPLE__
#include
@@ -18,6 +19,7 @@
#include "errors.h"
#include "runtime.h"
#include "internal.h"
+#include "highlight.h"
#include "descriptors.h"
#include "silver/engine.h"
@@ -358,6 +360,57 @@ static ant_value_t js_match(ant_t *js, ant_value_t *args, int nargs) {
return js_mkundef();
}
+static ant_value_t hl_get_tagged(ant_t *js, ant_value_t *args, int nargs) {
+ size_t input_len;
+ char *input = js_getstr(js, args[0], &input_len);
+
+ size_t out_size = input_len * 8 + 1;
+ char *out = malloc(out_size);
+ if (!out) return js_mkerr(js, "out of memory");
+
+ int len = ant_highlight(input, input_len, out, out_size);
+ ant_value_t result = js_mkstr(js, out, (size_t)len);
+ free(out);
+
+ return result;
+}
+
+static ant_value_t hl_render_tagged(ant_t *js, ant_value_t tagged) {
+ size_t tagged_len;
+ char *tagged_str = js_getstr(js, tagged, &tagged_len);
+
+ size_t ansi_size = tagged_len * 2 + 1;
+ char *ansi = malloc(ansi_size);
+ if (!ansi) return js_mkerr(js, "out of memory");
+
+ int len = crsprintf_stateful(ansi, ansi_size, NULL, tagged_str);
+ ant_value_t result = js_mkstr(js, ansi, len < 0 ? 0 : (size_t)len);
+ free(ansi);
+
+ return result;
+}
+
+static ant_value_t js_highlight(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1) return js_mkerr(js, "Ant.highlight() requires 1 argument");
+ if (vtype(args[0]) != T_STR) return js_mkerr(js, "Ant.highlight() argument must be a string");
+
+ ant_value_t tagged = hl_get_tagged(js, args, nargs);
+ if (is_err(tagged)) return tagged;
+ return hl_render_tagged(js, tagged);
+}
+
+static ant_value_t js_highlight_render(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1) return js_mkerr(js, "Ant.highlight.render() requires 1 argument");
+ if (vtype(args[0]) != T_STR) return js_mkerr(js, "Ant.highlight.render() argument must be a string");
+ return hl_render_tagged(js, args[0]);
+}
+
+static ant_value_t js_highlight_tags(ant_t *js, ant_value_t *args, int nargs) {
+ if (nargs < 1) return js_mkerr(js, "Ant.highlight.tags() requires 1 argument");
+ if (vtype(args[0]) != T_STR) return js_mkerr(js, "Ant.highlight.tags() argument must be a string");
+ return hl_get_tagged(js, args, nargs);
+}
+
void init_builtin_module() {
ant_t *js = rt->js;
ant_value_t ant_obj = rt->ant_obj;
@@ -368,6 +421,14 @@ void init_builtin_module() {
js_set(js, ant_obj, "sleep", js_mkfun(js_sleep));
js_set(js, ant_obj, "msleep", js_mkfun(js_msleep));
js_set(js, ant_obj, "usleep", js_mkfun(js_usleep));
+
+ ant_value_t hl_obj = js_newobj(js);
+ ant_value_t hl_fn = js_obj_to_func(hl_obj);
+
+ js_set_slot(hl_obj, SLOT_CFUNC, js_mkfun(js_highlight));
+ js_set(js, hl_fn, "render", js_mkfun(js_highlight_render));
+ js_set(js, hl_fn, "tags", js_mkfun(js_highlight_tags));
+ js_set(js, ant_obj, "highlight", hl_fn);
ant_value_t raw_obj = js_newobj(js);
js_set_getter_desc(js, js_as_obj(raw_obj), "stack", 5, js_mkfun(js_raw_stack), JS_DESC_C);
diff --git a/src/readline.c b/src/readline.c
index e2015e4..f071055 100644
--- a/src/readline.c
+++ b/src/readline.c
@@ -308,24 +308,20 @@ static void refresh_line(const char *line, int len, int pos, const char *prompt)
repl_clear_to_end();
if (crprintf_get_color() && len > 0) {
if (len <= 2048) {
- char tagged[8192];
- char rendered[8192];
-
- highlight_state state = hl_line_state;
- ant_highlight_stateful(line, (size_t)len, tagged, sizeof(tagged), &state);
-
- hl_prog = crprintf_recompile(hl_prog, tagged);
- crprintf_state *rs = crprintf_state_new();
- crsprintf_compiled(rendered, sizeof(rendered), rs, hl_prog);
- crprintf_state_free(rs);
-
- fputs(rendered, stdout);
- } else {
- fwrite(line, 1, (size_t)len, stdout);
- }
- } else if (len > 0) {
- fwrite(line, 1, (size_t)len, stdout);
- }
+ char tagged[8192];
+ char rendered[8192];
+
+ highlight_state state = hl_line_state;
+ ant_highlight_stateful(line, (size_t)len, tagged, sizeof(tagged), &state);
+
+ hl_prog = crprintf_recompile(hl_prog, tagged);
+ crprintf_state *rs = crprintf_state_new();
+ crsprintf_compiled(rendered, sizeof(rendered), rs, hl_prog);
+ crprintf_state_free(rs);
+
+ fputs(rendered, stdout);
+ } else fwrite(line, 1, (size_t)len, stdout);
+ } else if (len > 0) fwrite(line, 1, (size_t)len, stdout);
#ifndef _WIN32
if ((x_end == 0) && (y_end > 0) && (len > 0) && (line[len - 1] != '\n')) {