Compare commits

...

3 commits

Author SHA1 Message Date
1f6cb4eb0c
add help
All checks were successful
Generic zig build / build (push) Successful in 28s
2025-10-15 11:24:01 -07:00
bd6e09bf50
warn instead of fail when replacements config missing 2025-10-15 11:15:16 -07:00
5a38fd65c7
use replacements in a config file 2025-10-15 10:57:43 -07:00
4 changed files with 217 additions and 98 deletions

View file

@ -150,7 +150,12 @@ pub fn build(b: *std.Build) !void {
});
const options = b.addOptions();
options.addOption(bool, "long_tests", long_tests);
mod.addImport("build_options", options.createModule());
const git_describe = b.run(&.{ "git", "describe", "--always", "--dirty" });
options.addOption([]const u8, "version", git_describe);
const options_module = options.createModule();
mod.addImport("build_options", options_module);
mod.linkLibrary(lib);
mod.addIncludePath(upstream.path("include"));
@ -192,6 +197,7 @@ pub fn build(b: *std.Build) !void {
// can be extremely useful in case of collisions (which can happen
// importing modules from different packages).
.{ .name = "pos", .module = mod },
.{ .name = "build_options", .module = options_module },
},
}),
});

16
replacements.json Normal file
View file

@ -0,0 +1,16 @@
{
"late": "light",
"lake": "light",
"like": "light",
"life": "light",
"another": "on the",
"better": "bedroom",
"my": "light",
"night": "light",
"way": "light",
"me all": "emil",
"a meal": "emil",
"her": "turn",
"hearn": "turn",
"can't": ""
}

View file

@ -1,21 +1,59 @@
const builtin = @import("builtin");
const std = @import("std");
const pos = @import("pos");
const build_options = @import("build_options");
const word_replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "late", "light" },
.{ "lake", "light" },
.{ "like", "light" },
.{ "life", "light" },
.{ "another", "on the" },
.{ "better", "bedroom" },
.{ "my", "light" },
.{ "night", "light" },
.{ "way", "light" },
.{ "me all", "emil" },
.{ "a meal", "emil" },
.{ "her", "turn" },
});
fn loadReplacements(allocator: std.mem.Allocator) !std.StringHashMap([]const u8) {
var replacements = std.StringHashMap([]const u8).init(allocator);
// Try current directory first
if (std.fs.cwd().openFile("replacements.json", .{})) |file| {
defer file.close();
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
defer allocator.free(content);
const parsed = try std.json.parseFromSlice(std.json.Value, allocator, content, .{});
defer parsed.deinit();
const obj = parsed.value.object;
var it = obj.iterator();
while (it.next()) |entry| {
try replacements.put(try allocator.dupe(u8, entry.key_ptr.*), try allocator.dupe(u8, entry.value_ptr.*.string));
}
return replacements;
} else |_| {}
// Try XDG_CONFIG_HOME or HOME/.config
const config_dir = std.posix.getenv("XDG_CONFIG_HOME") orelse blk: {
const home = std.posix.getenv("HOME") orelse {
std.log.warn("No replacement configuration file found", .{});
return replacements;
};
break :blk try std.fs.path.join(allocator, &.{ home, ".config" });
};
const config_path = if (std.posix.getenv("XDG_CONFIG_HOME") != null)
try std.fs.path.join(allocator, &.{ config_dir, "pos", "replacements.json" })
else blk: {
defer allocator.free(config_dir);
break :blk try std.fs.path.join(allocator, &.{ config_dir, "pos", "replacements.json" });
};
defer allocator.free(config_path);
if (std.fs.openFileAbsolute(config_path, .{})) |file| {
defer file.close();
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
defer allocator.free(content);
const parsed = try std.json.parseFromSlice(std.json.Value, allocator, content, .{});
defer parsed.deinit();
const obj = parsed.value.object;
var it = obj.iterator();
while (it.next()) |entry| {
try replacements.put(try allocator.dupe(u8, entry.key_ptr.*), try allocator.dupe(u8, entry.value_ptr.*.string));
}
return replacements;
} else |_| {}
std.log.warn("No replacement configuration file found", .{});
return replacements;
}
const DeviceAction = enum {
on,
@ -201,7 +239,7 @@ fn parseAction(action_words: [][]const u8) ?DeviceAction {
return null;
}
fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devices: *std.StringHashMap([]const u8)) !?std.hash_map.StringHashMap([]const u8).Entry {
fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devices: *std.StringHashMap([]const u8), replacements: *std.StringHashMap([]const u8)) !?std.hash_map.StringHashMap([]const u8).Entry {
if (object_words.len == 0) return null;
var total_size: usize = 0;
@ -237,7 +275,7 @@ fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devic
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;
const final_word = replacements.get(lower_string) orelse lower_string;
try writer.print("{s}{s}", .{
final_word,
if (i < object_words.len - removed_words) " " else "",
@ -268,7 +306,27 @@ pub fn main() !u8 {
sentence_parse_only = .sentence
else if (std.mem.eql(u8, arg, "--command-parse-only"))
sentence_parse_only = .command
else if (sentence_arg == null)
else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
var stdout_writer = std.fs.File.stdout().writer(&.{});
const stdout = &stdout_writer.interface;
try stdout.print(
\\pos version {s}
\\
\\Usage: {s} [OPTIONS] <sentence>
\\
\\Options:
\\ --sentence-parse-only Parse sentence and show parse tree
\\ --command-parse-only Parse as command with replacements
\\ --help, -h Show this help message
\\
\\Configuration:
\\ Replacements are loaded from:
\\ 1. ./replacements.json (current directory)
\\ 2. XDG_CONFIG_HOME/pos/replacements.json
\\
, .{ build_options.version, args[0] });
return 0;
} else if (sentence_arg == null)
sentence_arg = arg;
}
@ -302,16 +360,24 @@ pub fn main() !u8 {
const sentence_z = try allocator.dupeZ(u8, sentence_arg.?);
defer allocator.free(sentence_z);
var tree = if (sentence_parse_only == .command)
parser.adaptiveCommandParse(sentence_z, word_replacements) catch {
std.log.err("Failed to parse sentence: {s}", .{sentence_z});
return 1;
var tree = if (sentence_parse_only == .command) blk: {
var replacements = try loadReplacements(allocator);
defer {
var iterator = replacements.iterator();
while (iterator.next()) |entry| {
allocator.free(entry.key_ptr.*);
allocator.free(entry.value_ptr.*);
}
replacements.deinit();
}
else
parser.parse(sentence_z) catch {
break :blk parser.adaptiveCommandParse(sentence_z, &replacements) catch {
std.log.err("Failed to parse sentence: {s}", .{sentence_z});
return 1;
};
} else parser.parse(sentence_z) catch {
std.log.err("Failed to parse sentence: {s}", .{sentence_z});
return 1;
};
defer tree.deinit();
try stdout.print("{f}", .{tree});
@ -331,7 +397,17 @@ pub fn main() !u8 {
devices.deinit();
}
processCommand(allocator, args[1], &parser, &devices) catch |err| {
var replacements = try loadReplacements(allocator);
defer {
var iterator = replacements.iterator();
while (iterator.next()) |entry| {
allocator.free(entry.key_ptr.*);
allocator.free(entry.value_ptr.*);
}
replacements.deinit();
}
processCommand(allocator, args[1], &parser, &devices, &replacements) catch |err| {
switch (err) {
error.UnrecognizedSentence => {
try stderr.print("Unrecognized sentence: {s}\n", .{args[1]});
@ -344,9 +420,9 @@ pub fn main() !u8 {
return 0;
}
fn processCommand(allocator: std.mem.Allocator, sentence: [:0]const u8, parser: *pos.Parser, devices: *std.StringHashMap([]const u8)) !void {
var tree = parser.adaptiveCommandParse(sentence, word_replacements) catch |err| {
std.log.err("Failed to parse sentence with all replacements: {}\n", .{err});
fn processCommand(allocator: std.mem.Allocator, sentence: [:0]const u8, parser: *pos.Parser, devices: *std.StringHashMap([]const u8), replacements: *std.StringHashMap([]const u8)) !void {
var tree = parser.adaptiveCommandParse(sentence, replacements) catch |err| {
std.log.err("Failed to parse sentence: {}\n", .{err});
return error.UnrecognizedSentence;
};
defer tree.deinit();
@ -379,7 +455,7 @@ fn processCommand(allocator: std.mem.Allocator, sentence: [:0]const u8, parser:
std.log.debug("{s}]", .{aw.written()});
if (parseAction(action_words)) |action| {
if (try extractDevice(allocator, object_words, devices)) |entry| {
if (try extractDevice(allocator, object_words, devices, replacements)) |entry| {
if (builtin.is_test)
testSendCommand(entry, action)
else
@ -391,7 +467,9 @@ fn processCommand(allocator: std.mem.Allocator, sentence: [:0]const u8, parser:
return error.UnrecognizedSentence;
}
// SAFETY: only used for testing, and in each case this is set before checking results
var test_device_entry: std.hash_map.StringHashMap([]const u8).Entry = undefined;
// SAFETY: only used for testing, and in each case this is set before checking results
var test_action: DeviceAction = undefined;
fn testSendCommand(device_entry: std.hash_map.StringHashMap([]const u8).Entry, action: DeviceAction) void {
test_device_entry = device_entry;
@ -400,12 +478,14 @@ fn testSendCommand(device_entry: std.hash_map.StringHashMap([]const u8).Entry, a
test "extractDevice single word exact match" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("kitchen", "192.168.1.100");
try devices.put("bedroom", "192.168.1.101");
var object_words = [_][]const u8{"kitchen"};
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*);
}
@ -413,11 +493,13 @@ test "extractDevice single word exact match" {
test "extractDevice single word case insensitive" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("kitchen", "192.168.1.100");
var object_words = [_][]const u8{"Kitchen"};
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*);
}
@ -425,11 +507,13 @@ test "extractDevice single word case insensitive" {
test "extractDevice two words exact match" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("bedroom light", "192.168.1.100");
var object_words = [_][]const u8{ "bedroom", "light" };
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*);
}
@ -437,11 +521,13 @@ test "extractDevice two words exact match" {
test "extractDevice two words case insensitive" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("bedroom light", "192.168.1.100");
var object_words = [_][]const u8{ "Bedroom", "Light" };
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*);
}
@ -449,11 +535,13 @@ test "extractDevice two words case insensitive" {
test "extractDevice not found" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("kitchen", "192.168.1.100");
var object_words = [_][]const u8{"bathroom"};
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expect(result == null);
}
@ -461,9 +549,11 @@ test "extractDevice not found" {
test "extractDevice empty words" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
var object_words = [_][]const u8{};
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expect(result == null);
}
@ -471,22 +561,27 @@ test "extractDevice empty words" {
test "extractDevice fallback to shorter match" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("kitchen", "192.168.1.100");
var object_words = [_][]const u8{ "kitchen", "ceiling", "light" };
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
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();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
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);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", result.?.value_ptr.*);
}
@ -494,17 +589,22 @@ test "extractDevice word replacement lake to light" {
test "extractDevice word replacement like to light" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("like", "light");
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);
const result = try extractDevice(std.testing.allocator, object_words[0..], &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.101", result.?.value_ptr.*);
}
test "processCommand successful match" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("kitchen", "192.168.1.100");
@ -515,7 +615,7 @@ test "processCommand successful match" {
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", test_device_entry.value_ptr.*);
try std.testing.expectEqual(DeviceAction.on, test_action);
@ -524,6 +624,9 @@ test "processCommand successful match" {
test "processCommand with word replacement lake to light" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
try devices.put("kitchen light", "192.168.1.100");
@ -534,7 +637,7 @@ test "processCommand with word replacement lake to light" {
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.100", test_device_entry.value_ptr.*);
try std.testing.expectEqual(DeviceAction.on, test_action);
@ -543,6 +646,9 @@ test "processCommand with word replacement lake to light" {
test "processCommand with word replacement like to light" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("like", "light");
try devices.put("bedroom light", "192.168.1.101");
@ -553,7 +659,7 @@ test "processCommand with word replacement like to light" {
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.101", test_device_entry.value_ptr.*);
try std.testing.expectEqual(DeviceAction.off, test_action);
@ -561,6 +667,9 @@ test "processCommand with word replacement like to light" {
test "processCommand with word replacement like to light - three words" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("like", "light");
try devices.put("jack bedroom light", "192.168.1.101");
@ -571,10 +680,7 @@ test "processCommand with word replacement like to light - three words" {
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
// const ll = std.testing.log_level;
// std.testing.log_level = .debug;
// defer std.testing.log_level = ll;
try processCommand(std.testing.allocator, sentence_z, &parser, &devices);
try processCommand(std.testing.allocator, sentence_z, &parser, &devices, &replacements);
try std.testing.expectEqualStrings("192.168.1.101", test_device_entry.value_ptr.*);
try std.testing.expectEqual(DeviceAction.off, test_action);
@ -583,6 +689,8 @@ test "processCommand with word replacement like to light - three words" {
test "processCommand no match found" {
var devices = std.StringHashMap([]const u8).init(std.testing.allocator);
defer devices.deinit();
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try devices.put("kitchen", "192.168.1.100");
@ -593,6 +701,5 @@ test "processCommand no match found" {
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z);
// Should return UnrecognizedSentence error
try std.testing.expectError(error.UnrecognizedSentence, processCommand(std.testing.allocator, sentence_z, &parser, &devices));
try std.testing.expectError(error.UnrecognizedSentence, processCommand(std.testing.allocator, sentence_z, &parser, &devices, &replacements));
}

View file

@ -362,15 +362,14 @@ pub const Parser = struct {
_ = c.dictionary_delete(self.dict);
}
fn applyReplacements(self: *Parser, sentence: []const u8, replacements: std.StaticStringMap([]const u8), final_buf: []u8) ![]const u8 {
_ = self; // we don't want to remove this completely, as there
// could be a time when we need to re-parse after replacement
const replacement_keys = replacements.keys();
const replacement_values = replacements.values();
fn applyReplacements(self: *Parser, sentence: []const u8, replacements: *const std.StringHashMap([]const u8), final_buf: []u8) ![]const u8 {
_ = self;
var altered = sentence;
var it = replacements.iterator();
for (replacement_keys, replacement_values) |k, v| {
while (it.next()) |entry| {
const k = entry.key_ptr.*;
const v = entry.value_ptr.*;
var k_buf: [256]u8 = undefined;
var v_buf: [256]u8 = undefined;
@ -543,7 +542,7 @@ pub const Parser = struct {
/// sentence is found, it will be returned, with the guarantee that
/// sentenceObject and sentenceAction will return non-zero results. If that
/// condition cannot be satisfied, error.NoValidParse will be returned
pub fn adaptiveCommandParse(self: *Parser, sentence: []const u8, replacements: std.StaticStringMap([]const u8)) !ParseTree {
pub fn adaptiveCommandParse(self: *Parser, sentence: []const u8, replacements: *const std.StringHashMap([]const u8)) !ParseTree {
var final_buf: [1024]u8 = undefined;
var altered = try self.applyReplacements(sentence, replacements, &final_buf);
@ -788,13 +787,13 @@ test "adaptiveCommandParse 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" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
try replacements.put("like", "light");
const sentence = "turn on the kitchen light";
var tree = try parser.adaptiveCommandParse(sentence, replacements);
var tree = try parser.adaptiveCommandParse(sentence, &replacements);
defer tree.deinit();
const action_words = try tree.sentenceAction();
@ -814,14 +813,14 @@ test "adaptiveCommandParse 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" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
try replacements.put("like", "light");
const sentence = "turn on the kitchen lake";
var tree = try parser.adaptiveCommandParse(sentence, replacements);
var tree = try parser.adaptiveCommandParse(sentence, &replacements);
defer tree.deinit();
const action_words = try tree.sentenceAction();
@ -841,22 +840,16 @@ test "adaptiveCommandParse 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" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
try replacements.put("like", "light");
const sentence = "xyz abc def";
// const ll = std.testing.log_level;
// defer std.testing.log_level = ll;
// std.testing.log_level = .debug;
try std.testing.expectError(
error.SentenceEmptyAfterNullRemoval,
parser.adaptiveCommandParse(
sentence,
replacements,
),
parser.adaptiveCommandParse(sentence, &replacements),
);
}
@ -864,17 +857,14 @@ test "adaptiveCommandParse with word replacement and null removal" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "lake", "light" },
.{ "like", "light" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
try replacements.put("like", "light");
const sentence = "alexa turn on the kitchen lake";
// const ll = std.testing.log_level;
// defer std.testing.log_level = ll;
// std.testing.log_level = .debug;
var tree = try parser.adaptiveCommandParse(sentence, replacements);
var tree = try parser.adaptiveCommandParse(sentence, &replacements);
defer tree.deinit();
const action_words = try tree.sentenceAction();
@ -893,12 +883,12 @@ test "applyReplacements basic replacement" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "lake", "light" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
var final_buf: [1024]u8 = undefined;
const result = try parser.applyReplacements("turn on the kitchen lake", replacements, &final_buf);
const result = try parser.applyReplacements("turn on the kitchen lake", &replacements, &final_buf);
try std.testing.expectEqualStrings("turn on the kitchen light", result);
}
@ -906,13 +896,13 @@ test "applyReplacements multiple replacements" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "lake", "light" },
.{ "kitchen", "bedroom" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("lake", "light");
try replacements.put("kitchen", "bedroom");
var final_buf: [1024]u8 = undefined;
const result = try parser.applyReplacements("turn on the kitchen lake", replacements, &final_buf);
const result = try parser.applyReplacements("turn on the kitchen lake", &replacements, &final_buf);
try std.testing.expectEqualStrings("turn on the bedroom light", result);
}
@ -920,12 +910,12 @@ test "applyReplacements empty after replacement" {
var parser = try Parser.init(std.testing.allocator);
defer parser.deinit();
const replacements = std.StaticStringMap([]const u8).initComptime(.{
.{ "test", "" },
});
var replacements = std.StringHashMap([]const u8).init(std.testing.allocator);
defer replacements.deinit();
try replacements.put("test", "");
var final_buf: [1024]u8 = undefined;
try std.testing.expectError(error.SentenceEmptyAfterReplacements, parser.applyReplacements("test", replacements, &final_buf));
try std.testing.expectError(error.SentenceEmptyAfterReplacements, parser.applyReplacements("test", &replacements, &final_buf));
}
test "removeNullWords no nulls" {