replace words in original sentence

This commit is contained in:
Emil Lerch 2025-09-20 15:33:21 -07:00
parent 47a6ffb1e3
commit 78a059e9ef
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -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} <sentence>\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.*);
}