From 78a059e9ef393f6dc41a8439d63f22af7916bb01 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 20 Sep 2025 15:33:21 -0700 Subject: [PATCH] replace words in original sentence --- src/main.zig | 194 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 143 insertions(+), 51 deletions(-) diff --git a/src/main.zig b/src/main.zig index 4585c17..501a446 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,11 @@ const std = @import("std"); const pos = @import("pos"); +const word_replacements = std.StaticStringMap([]const u8).initComptime(.{ + .{ "lake", "light" }, + .{ "like", "light" }, +}); + const DeviceAction = enum { on, off, @@ -103,7 +108,7 @@ fn loadDeviceConfig(allocator: std.mem.Allocator, bin_dir: []const u8) !std.Stri const name_copy = try allocator.alloc(u8, name.len); _ = std.ascii.lowerString(name_copy, name); try devices.put(name_copy, try allocator.dupe(u8, url)); - std.log.debug("Loaded device: '{s}' -> '{s}'\n", .{ name, url }); + std.log.debug("Loaded device: '{s}' -> {s}", .{ name, url }); } } @@ -175,6 +180,8 @@ fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devic var allocating_writer = try std.Io.Writer.Allocating.initCapacity(allocator, total_size); defer allocating_writer.deinit(); const writer = &allocating_writer.writer; + + // Try original words first for (0..object_words.len) |removed_words| { defer allocating_writer.clearRetainingCapacity(); const total_words = object_words.len - removed_words; @@ -188,100 +195,162 @@ fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devic }); } - std.log.debug( - "Attempting device match '{s}'", - .{allocating_writer.written()}, - ); + std.log.debug("Attempting device match '{s}'", .{allocating_writer.written()}); if (devices.getEntry(allocating_writer.written())) |entry| return entry; - std.log.info( - "Could not find device match '{s}'. Falling back to more general object", - .{allocating_writer.written()}, - ); } + + // Try with word replacements + for (0..object_words.len) |removed_words| { + defer allocating_writer.clearRetainingCapacity(); + const total_words = object_words.len - removed_words; + for (object_words, 1..) |word, i| { + if (i >= total_words + 1) break; + var buf: [256]u8 = undefined; + const lower_string = std.ascii.lowerString(&buf, word); + const final_word = word_replacements.get(lower_string) orelse lower_string; + try writer.print("{s}{s}", .{ + final_word, + if (i < object_words.len - removed_words) " " else "", + }); + } + + std.log.debug("Attempting device match with replacements '{s}'", .{allocating_writer.written()}); + if (devices.getEntry(allocating_writer.written())) |entry| return entry; + } + return null; } -pub fn main() !void { +pub fn main() !u8 { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); var stdout_writer = std.fs.File.stdout().writer(&.{}); const stdout = &stdout_writer.interface; + _ = stdout; var stderr_writer = std.fs.File.stderr().writer(&.{}); const stderr = &stderr_writer.interface; if (args.len < 2) { stderr.print("Usage: {s} \n", .{args[0]}) catch {}; - std.process.exit(1); + return 1; } std.log.debug("loading device config", .{}); // Get binary directory - const bin_path = std.fs.selfExePathAlloc(gpa.allocator()) catch |err| { + const bin_path = std.fs.selfExePathAlloc(allocator) catch |err| { stderr.print("Failed to get binary path: {}\n", .{err}) catch {}; - std.process.exit(1); + return 1; }; - defer gpa.allocator().free(bin_path); + defer allocator.free(bin_path); const bin_dir = std.fs.path.dirname(bin_path) orelse "."; - var devices = loadDeviceConfig(gpa.allocator(), bin_dir) catch |err| { + var devices = loadDeviceConfig(allocator, bin_dir) catch |err| { stderr.print("Failed to load device configuration: {}\n", .{err}) catch {}; - std.process.exit(1); + return 1; }; defer { var iterator = devices.iterator(); while (iterator.next()) |entry| { - gpa.allocator().free(entry.key_ptr.*); - gpa.allocator().free(entry.value_ptr.*); + allocator.free(entry.key_ptr.*); + allocator.free(entry.value_ptr.*); } devices.deinit(); } std.log.debug("initializing parser", .{}); - var parser = pos.Parser.initWithDataDir(gpa.allocator(), bin_dir) catch |err| { + var parser = pos.Parser.initWithDataDir(allocator, bin_dir) catch |err| { stderr.print("Failed to initialize parser: {}\n", .{err}) catch {}; - std.process.exit(1); + return 1; }; defer parser.deinit(); - var tree = parser.parse(args[1]) catch |err| { - stderr.print("Failed to parse sentence: {}\n", .{err}) catch {}; - std.process.exit(1); - }; - defer tree.deinit(); + // Try original sentence first, then with word replacements + const replacement_keys = word_replacements.keys(); + const replacement_values = word_replacements.values(); - const action_words = tree.sentenceAction() catch |err| { - stderr.print("Failed to extract action: {}\n", .{err}) catch {}; - std.process.exit(1); - }; - defer gpa.allocator().free(action_words); + var sentence_to_try: [:0]const u8 = args[1]; + var replaced_sentence: ?[:0]u8 = null; + defer if (replaced_sentence) |s| allocator.free(s); - const object_words = tree.sentenceObject() catch |err| { - stderr.print("Failed to extract object: {}\n", .{err}) catch {}; - std.process.exit(1); - }; - defer gpa.allocator().free(object_words); - - std.debug.print("Object words: ", .{}); - for (object_words) |word| { - std.debug.print("'{s}' ", .{word}); - } - std.debug.print("\n", .{}); - - if (parseAction(action_words)) |action| { - if (try extractDevice(allocator, object_words, &devices)) |entry| { - try sendWemoCommand(entry, action, gpa.allocator()); - } else { - try stdout.print("Device not found in configuration\n", .{}); + var success = false; + for (0..replacement_keys.len + 1) |attempt| { + if (attempt > 0) { + // Create sentence with replacements + if (replaced_sentence) |s| allocator.free(s); + const temp = try std.mem.replaceOwned( + u8, + allocator, + sentence_to_try, + replacement_keys[attempt - 1], + replacement_values[attempt - 1], + ); + replaced_sentence = try allocator.dupeZ(u8, temp); + allocator.free(temp); + if (std.mem.eql(u8, sentence_to_try, replaced_sentence.?)) continue; + sentence_to_try = replaced_sentence.?; + std.log.info("Replaced word '{s}' in sentence with replacement '{s}'. Trying sentence: {s}", .{ + replacement_keys[attempt - 1], + replacement_values[attempt - 1], + sentence_to_try, + }); + } + + var tree = parser.parse(sentence_to_try) catch |err| { + std.log.err("Failed to parse sentence: {}\n", .{err}); + continue; + }; + defer tree.deinit(); + + const action_words = tree.sentenceAction() catch |err| { + std.log.err("Failed to extract action: {}\n", .{err}); + continue; + }; + defer allocator.free(action_words); + if (action_words.len == 0) { + std.log.info("Failed to extract action from sentence", .{}); + continue; + } + + const object_words = tree.sentenceObject() catch |err| { + std.log.err("Failed to extract object: {}\n", .{err}); + continue; + }; + defer allocator.free(object_words); + if (object_words.len == 0) { + std.log.info("Failed to extract object from sentence", .{}); + continue; + } + + var aw = std.Io.Writer.Allocating.init(allocator); + defer aw.deinit(); + const aw_writer = &aw.writer; + try aw_writer.writeAll("Object words: ["); + var first = true; + for (object_words) |word| { + try aw_writer.print("{s}\"{s}\"", .{ if (!first) ", " else "", word }); + first = false; + } + std.log.debug("{s}]", .{aw.written()}); + + if (parseAction(action_words)) |action| { + if (try extractDevice(allocator, object_words, &devices)) |entry| { + try sendWemoCommand(entry, action, allocator); + success = true; + break; // Success, exit + } } - } else { - try stdout.print("Unrecognized sentence: {s}\n", .{args[1]}); } + if (!success) { + try stderr.print("Unrecognized sentence: {s}\n", .{args[1]}); + return 1; + } + return 0; } test "extractDevice single word exact match" { var devices = std.StringHashMap([]const u8).init(std.testing.allocator); @@ -365,3 +434,26 @@ test "extractDevice fallback to shorter match" { try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*); } +test "extractDevice word replacement lake to light" { + var devices = std.StringHashMap([]const u8).init(std.testing.allocator); + defer devices.deinit(); + + try devices.put("bedroom light", "192.168.1.100"); + + var object_words = [_][]const u8{ "bedroom", "lake" }; + const result = try extractDevice(std.testing.allocator, object_words[0..], &devices); + + try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*); +} + +test "extractDevice word replacement like to light" { + var devices = std.StringHashMap([]const u8).init(std.testing.allocator); + defer devices.deinit(); + + try devices.put("kitchen light", "192.168.1.101"); + + var object_words = [_][]const u8{ "kitchen", "like" }; + const result = try extractDevice(std.testing.allocator, object_words[0..], &devices); + + try std.testing.expectEqualStrings("192.168.1.101", result.?.value_ptr.*); +}