* Added support for . (any characer) token in grammar engine.
* Add integration tests for any-character symbol.
throw std::runtime_error(std::string("expecting ')' at ") + pos);
}
pos = parse_space(pos + 1, is_nested);
+ } else if (*pos == '.') { // any char
+ last_sym_start = out_elements.size();
+ out_elements.push_back({LLAMA_GRETYPE_CHAR_ANY, 0});
+ pos = parse_space(pos + 1, is_nested);
} else if (*pos == '*') {
pos = parse_space(pos + 1, is_nested);
handle_repetitions(0, -1);
case LLAMA_GRETYPE_CHAR_NOT: return true;
case LLAMA_GRETYPE_CHAR_ALT: return true;
case LLAMA_GRETYPE_CHAR_RNG_UPPER: return true;
+ case LLAMA_GRETYPE_CHAR_ANY: return true;
default: return false;
}
}
case LLAMA_GRETYPE_CHAR_NOT: fprintf(file, "CHAR_NOT"); break;
case LLAMA_GRETYPE_CHAR_RNG_UPPER: fprintf(file, "CHAR_RNG_UPPER"); break;
case LLAMA_GRETYPE_CHAR_ALT: fprintf(file, "CHAR_ALT"); break;
+ case LLAMA_GRETYPE_CHAR_ANY: fprintf(file, "CHAR_ANY"); break;
}
switch (elem.type) {
case LLAMA_GRETYPE_END:
case LLAMA_GRETYPE_CHAR_NOT:
case LLAMA_GRETYPE_CHAR_RNG_UPPER:
case LLAMA_GRETYPE_CHAR_ALT:
+ case LLAMA_GRETYPE_CHAR_ANY:
fprintf(file, "(\"");
print_grammar_char(file, elem.value);
fprintf(file, "\") ");
}
print_grammar_char(file, elem.value);
break;
+ case LLAMA_GRETYPE_CHAR_ANY:
+ fprintf(file, ".");
+ break;
}
if (is_char_element(elem)) {
switch (rule[i + 1].type) {
case LLAMA_GRETYPE_CHAR_ALT:
case LLAMA_GRETYPE_CHAR_RNG_UPPER:
+ case LLAMA_GRETYPE_CHAR_ANY:
break;
default:
fprintf(file, "] ");
const uint32_t chr) {
bool found = false;
- bool is_positive_char = pos->type == LLAMA_GRETYPE_CHAR;
+ bool is_positive_char = pos->type == LLAMA_GRETYPE_CHAR || pos->type == LLAMA_GRETYPE_CHAR_ANY;
GGML_ASSERT(is_positive_char || pos->type == LLAMA_GRETYPE_CHAR_NOT); // NOLINT
// inclusive range, e.g. [a-z]
found = found || (pos->value <= chr && chr <= pos[1].value);
pos += 2;
+ } else if (pos->type == LLAMA_GRETYPE_CHAR_ANY) {
+ // Any character matches "."
+ found = true;
+ pos += 1;
} else {
// exact char match, e.g. [a] or "a"
found = found || pos->value == chr;
const llama_grammar_element * pos,
const llama_partial_utf8 partial_utf8) {
- bool is_positive_char = pos->type == LLAMA_GRETYPE_CHAR;
+ bool is_positive_char = pos->type == LLAMA_GRETYPE_CHAR || pos->type == LLAMA_GRETYPE_CHAR_ANY;
GGML_ASSERT(is_positive_char || pos->type == LLAMA_GRETYPE_CHAR_NOT);
uint32_t partial_value = partial_utf8.value;
return is_positive_char;
}
pos += 2;
+ } else if (pos->type == LLAMA_GRETYPE_CHAR_ANY) {
+ // Any character matches "."
+ return true;
} else {
// exact char match, e.g. [a] or "a"
if (low <= pos->value && pos->value <= high) {
}
case LLAMA_GRETYPE_CHAR:
case LLAMA_GRETYPE_CHAR_NOT:
+ case LLAMA_GRETYPE_CHAR_ANY:
if (std::find(new_stacks.begin(), new_stacks.end(), stack) == new_stacks.end()) {
// only add the stack if it's not a duplicate of one we already have
new_stacks.emplace_back(stack);
// modifies a preceding LLAMA_GRETYPE_CHAR or
// LLAMA_GRETYPE_CHAR_RNG_UPPER to add an alternate char to match ([ab], [a-zA])
LLAMA_GRETYPE_CHAR_ALT = 6,
+
+ // any character (.)
+ LLAMA_GRETYPE_CHAR_ANY = 7,
};
typedef struct llama_grammar_element {
);
}
+static void test_special_chars() {
+ // A collection of tests to exercise special characters such as "."
+ test_grammar(
+ "special characters",
+ // Grammar
+ R"""(
+ root ::= ... "abc" ...
+ )""",
+ // Passing strings
+ {
+ "abcabcabc",
+ "aaaabcccc",
+ // NOTE: Also ensures that multi-byte characters still count as a single character
+ "🔵🟠✅abc❌🟠🔵"
+ },
+ // Failing strings
+ {
+ "aaabcccc",
+ "aaaaabcccc",
+ "aaaabccc",
+ "aaaabccccc",
+ "🔵🟠✅❌abc❌✅🟠🔵"
+ "🔵🟠abc🟠🔵"
+ }
+ );
+}
+
static void test_quantifiers() {
// A collection of tests to exercise * + and ? quantifiers
fprintf(stdout, "Running grammar integration tests...\n");
test_simple_grammar();
test_complex_grammar();
+ test_special_chars();
test_quantifiers();
test_failure_missing_root();
test_failure_missing_reference();