add ai-generated ASCII-art link diagram

This commit is contained in:
Emil Lerch 2025-09-22 16:50:52 -07:00
parent a0a162632e
commit e63f0024ce
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -54,6 +54,9 @@ pub const ParseTree = struct {
}
}
pub fn format(self: ParseTree, writer: *std.io.Writer) std.io.Writer.Error!void {
// Print ASCII link diagram
try self.printLinkDiagram(writer);
try writer.writeAll("Words: ");
for (self.words, 0..) |word, i| {
try writer.print("{d}: '{s}' ", .{ i, word });
@ -61,7 +64,7 @@ pub const ParseTree = struct {
try writer.print("\n\nLinks ({} total):\n", .{self.links.len});
for (self.links, 0..) |link, i| {
try writer.print(" [{d}] '{s}' --{s}--> '{s}'\n", .{ i, link.left_word, link.label, link.right_word });
try writer.print(" [{d}] {s} --{s}--> {s}\n", .{ i, link.left_word, link.label, link.right_word });
}
try writer.writeAll("\nConstituent Tree:\n");
@ -71,6 +74,87 @@ pub const ParseTree = struct {
try writer.writeAll(" (no constituent tree)\n");
}
}
fn printLinkDiagram(self: ParseTree, writer: *std.io.Writer) !void {
// Simple ASCII diagram - just print links above words
const max_width = 200;
var line_buffer: [max_width]u8 = undefined;
// Calculate word positions
var word_positions: [20]usize = undefined; // max 20 words
var total_width: usize = 0;
for (self.words, 0..) |word, i| {
if (i >= word_positions.len) break;
word_positions[i] = total_width;
total_width += word.len + 1;
}
// Print a simple link line
@memset(&line_buffer, ' ');
for (self.links) |link| {
const left_idx = self.findWordIndex(link.left_word) orelse continue;
const right_idx = self.findWordIndex(link.right_word) orelse continue;
if (left_idx >= word_positions.len or right_idx >= word_positions.len) continue;
const left_pos = word_positions[left_idx] + link.left_word.len / 2;
const right_pos = word_positions[right_idx] + link.right_word.len / 2;
const start_pos = @min(left_pos, right_pos);
const end_pos = @max(left_pos, right_pos);
if (end_pos < max_width) {
if (start_pos < max_width) line_buffer[start_pos] = '+';
if (end_pos < max_width) line_buffer[end_pos] = '+';
for (start_pos + 1..end_pos) |i| {
if (i < max_width) line_buffer[i] = '-';
}
// Add label
const label_start = (start_pos + end_pos) / 2;
if (label_start >= link.label.len / 2 and label_start + link.label.len < max_width) {
const label_pos = label_start - link.label.len / 2;
for (link.label, 0..) |ch, i| {
if (label_pos + i < max_width) {
line_buffer[label_pos + i] = ch;
}
}
}
}
}
try writer.print("{s}\n", .{line_buffer[0..@min(total_width, max_width)]});
// Print connector line
@memset(&line_buffer, ' ');
for (self.links) |link| {
const left_idx = self.findWordIndex(link.left_word) orelse continue;
const right_idx = self.findWordIndex(link.right_word) orelse continue;
if (left_idx >= word_positions.len or right_idx >= word_positions.len) continue;
const left_pos = word_positions[left_idx] + link.left_word.len / 2;
const right_pos = word_positions[right_idx] + link.right_word.len / 2;
if (left_pos < max_width) line_buffer[left_pos] = '|';
if (right_pos < max_width) line_buffer[right_pos] = '|';
}
try writer.print("{s}\n", .{line_buffer[0..@min(total_width, max_width)]});
// Print words
for (self.words, 0..) |word, i| {
if (i > 0) try writer.writeAll(" ");
try writer.print("{s}", .{word});
}
try writer.writeAll("\n\n");
}
fn findWordIndex(self: ParseTree, word: []const u8) ?usize {
for (self.words, 0..) |w, i| {
if (std.mem.eql(u8, w, word)) return i;
}
return null;
}
pub fn firstVerb(self: *ParseTree) ?[]const u8 {
for (self.words) |word| {
if (std.mem.endsWith(u8, word, ".v")) {
@ -188,7 +272,7 @@ pub const ParseTree = struct {
for (0..depth) |_| {
try writer.writeAll(" ");
}
try writer.print("[{s}] ({}-{})\n", .{ node.label, node.start, node.end });
try writer.print("{s} [{}-{}]\n", .{ node.label, node.start, node.end });
if (node.child) |child| {
try self.printConstituentNode(writer, child, depth + 1);
}
@ -536,23 +620,23 @@ test "real usage - jack" {
test "adaptiveParse successful without replacements" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "lake", "light" },
.{ "like", "light" },
});
const sentence = "turn on the kitchen light";
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
var tree = try parser.adaptiveParse(sentence_z, replacements);
defer tree.deinit();
const action_words = try tree.sentenceAction();
defer std.testing.allocator.free(action_words);
try std.testing.expect(action_words.len > 0);
const object_words = try tree.sentenceObject();
defer std.testing.allocator.free(object_words);
try std.testing.expect(object_words.len > 0);
@ -561,23 +645,23 @@ test "adaptiveParse successful without replacements" {
test "adaptiveParse with word replacement" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "lake", "light" },
.{ "like", "light" },
});
const sentence = "turn on the kitchen lake";
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
var tree = try parser.adaptiveParse(sentence_z, replacements);
defer tree.deinit();
const action_words = try tree.sentenceAction();
defer std.testing.allocator.free(action_words);
try std.testing.expect(action_words.len > 0);
const object_words = try tree.sentenceObject();
defer std.testing.allocator.free(object_words);
try std.testing.expect(object_words.len > 0);
@ -586,15 +670,15 @@ test "adaptiveParse with word replacement" {
test "adaptiveParse no valid parse" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "lake", "light" },
.{ "like", "light" },
});
const sentence = "xyz abc def";
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
try std.testing.expectError(error.NoValidParse, parser.adaptiveParse(sentence_z, replacements));
}