diff --git a/src/root.zig b/src/root.zig index c79b326..7432b9c 100644 --- a/src/root.zig +++ b/src/root.zig @@ -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)); }