add test for anticipated language + code to make it pass

This commit is contained in:
Emil Lerch 2025-09-23 12:29:49 -07:00
parent 150ee53ec7
commit 5bc8a5c266
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -365,40 +365,97 @@ pub const Parser = struct {
/// sentenceObject and sentenceAction will return non-zero results. If that /// sentenceObject and sentenceAction will return non-zero results. If that
/// condition cannot be satisfied, error.NoValidParse will be returned /// condition cannot be satisfied, error.NoValidParse will be returned
pub fn adaptiveParse(self: *Parser, sentence: [:0]const u8, replacements: std.StaticStringMap([]const u8)) !ParseTree { pub fn adaptiveParse(self: *Parser, sentence: [:0]const u8, replacements: std.StaticStringMap([]const u8)) !ParseTree {
var sentence_to_try: [:0]const u8 = sentence; var altered_buf: [1024]u8 = undefined;
var replaced_sentence: ?[:0]u8 = null;
defer if (replaced_sentence) |s| self.allocator.free(s);
const replacement_keys = replacements.keys(); const replacement_keys = replacements.keys();
const replacement_values = replacements.values(); const replacement_values = replacements.values();
for (0..replacement_keys.len + 1) |attempt| { var altered = sentence;
if (attempt > 0) {
// Create sentence with replacements // Step 1: Replacements
if (replaced_sentence) |s| self.allocator.free(s); for (replacement_keys, replacement_values) |key, value| {
const temp = try std.mem.replaceOwned( const altered_size = std.mem.replacementSize(
u8, u8,
self.allocator, altered,
sentence_to_try, key,
replacement_keys[attempt - 1], value,
replacement_values[attempt - 1],
); );
replaced_sentence = try self.allocator.dupeZ(u8, temp); if (altered_size > 1023) {
self.allocator.free(temp); if (shouldLog())
if (std.mem.eql(u8, sentence_to_try, replaced_sentence.?)) continue; std.log.err("Sentence too long (>1023): {s}", .{altered});
sentence_to_try = replaced_sentence.?; return error.SentenceTooLong;
std.log.info("Replaced word '{s}' in sentence with replacement '{s}'. Trying sentence: {s}", .{ }
replacement_keys[attempt - 1], const replacement_count = std.mem.replace(
replacement_values[attempt - 1], u8,
sentence_to_try, altered,
key,
value,
&altered_buf,
);
altered_buf[altered_size] = 0; // add sentinel
altered = altered_buf[0..altered_size :0];
if (replacement_count > 0)
// we have altered the deal. Pray we don't alter it further
std.log.info("Replaced '{s}' in sentence with replacement '{s}' {d} times. Sentence now:\n\t{s}", .{
key,
value,
replacement_count,
altered,
}); });
} }
var tree = self.parse(altered) catch |err| {
var tree = self.parse(sentence_to_try) catch |err| { if (shouldLog())
std.log.err("Failed to parse sentence: {}\n", .{err}); std.log.err("Failed to parse sentence: {}\n\t{s}", .{ err, altered });
continue; // continue;
return err;
}; };
std.log.debug("adaptiveParse (step 1 - replacements):\n\toriginal:\n\t\t{s}\n\taltered:\n\t\t{s}\n{f}", .{
sentence,
altered,
tree,
});
// Step 2: replace null words
var nulls_removed = true;
while (nulls_removed) {
nulls_removed = false;
for (tree.words) |word| {
if (std.mem.indexOf(u8, word, "[?]")) |i| {
nulls_removed = true;
// We need to alter this further
const trimmed = word[0..i];
const removals = std.mem.replace(
u8,
altered,
trimmed,
"",
&altered_buf,
);
const len = altered.len - (removals * trimmed.len);
altered_buf[len] = 0;
altered = altered_buf[0..len :0];
std.log.info("Removed null word '{s}' in sentence {d} time(s). Sentence now:\n\t{s}", .{
trimmed,
removals,
altered,
});
// Retry parsing with the word removed
tree.deinit();
tree = self.parse(altered) catch |err| {
if (shouldLog())
std.log.err("Failed to parse altered sentence: {}\n\t{s}", .{ err, altered });
// continue;
return err;
};
break; // we will remove these words conservatively...
}
}
}
// Bracketed words are "null" // Bracketed words are "null"
// words with [?] are "unknown" // words with [?] are "unknown"
// If we have unknowns, I think we want to replace (or if no replacement // If we have unknowns, I think we want to replace (or if no replacement
@ -418,39 +475,42 @@ pub const Parser = struct {
// Validate that we can extract action and object before returning // Validate that we can extract action and object before returning
const action_words = tree.sentenceAction() catch |err| { const action_words = tree.sentenceAction() catch |err| {
if (!builtin.is_test) if (shouldLog())
std.log.err("Failed to extract action: {}\n", .{err}); std.log.err("Failed to extract action: {}\n", .{err});
tree.deinit(); tree.deinit();
continue; return err;
// continue;
}; };
defer self.allocator.free(action_words); defer self.allocator.free(action_words);
if (action_words.len == 0) { if (action_words.len == 0) {
if (!builtin.is_test)
std.log.info("Failed to extract action from sentence", .{}); std.log.info("Failed to extract action from sentence", .{});
tree.deinit(); tree.deinit();
continue; return error.SentenceActionNotFound;
// continue;
} }
const object_words = tree.sentenceObject() catch |err| { const object_words = tree.sentenceObject() catch |err| {
if (!builtin.is_test) if (shouldLog())
std.log.err("Failed to extract object: {}\n", .{err}); std.log.err("Failed to extract object: {}\n", .{err});
tree.deinit(); tree.deinit();
continue; // continue;
return err;
}; };
defer self.allocator.free(object_words); defer self.allocator.free(object_words);
if (object_words.len == 0) { if (object_words.len == 0) {
if (!builtin.is_test)
std.log.info("Failed to extract object from sentence", .{}); std.log.info("Failed to extract object from sentence", .{});
tree.deinit(); tree.deinit();
continue; // continue;
return error.SentenceObjectNotFound;
} }
return tree; return tree;
} }
return error.NoValidParse; inline fn shouldLog() bool {
return !builtin.is_test or std.testing.log_level != .warn; // .warn is default testing log level
} }
pub fn parse(self: *Parser, input: []const u8) !ParseTree { pub fn parse(self: *Parser, input: []const u8) !ParseTree {
@ -688,5 +748,43 @@ test "adaptiveParse no valid parse" {
const sentence_z = try std.testing.allocator.dupeZ(u8, sentence); const sentence_z = try std.testing.allocator.dupeZ(u8, sentence);
defer std.testing.allocator.free(sentence_z); defer std.testing.allocator.free(sentence_z);
try std.testing.expectError(error.NoValidParse, parser.adaptiveParse(sentence_z, replacements)); // const ll = std.testing.log_level;
// defer std.testing.log_level = ll;
// std.testing.log_level = .debug;
try std.testing.expectError(
error.SentenceCreationFailed,
parser.adaptiveParse(
sentence_z,
replacements,
),
);
}
test "adaptiveParse 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" },
});
const sentence = "alexa 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 == 2);
try std.testing.expectEqualStrings("turn", action_words[0]);
try std.testing.expectEqualStrings("on", action_words[1]);
const object_words = try tree.sentenceObject();
defer std.testing.allocator.free(object_words);
try std.testing.expect(object_words.len == 2);
try std.testing.expectEqualStrings("kitchen", object_words[0]);
try std.testing.expectEqualStrings("light", object_words[1]);
} }