mirror of
				https://github.com/ggml-org/llama.cpp.git
				synced 2025-10-31 08:51:55 +00:00 
			
		
		
		
	json: support integer minimum, maximum, exclusiveMinimum, exclusiveMaximum (#7797)
				
					
				
			* json: support minimum for positive integer values * json: fix min 0 * json: min + max integer constraints * json: handle negative min / max integer bounds * json: fix missing paren min/max bug * json: proper paren fix * json: integration test for schemas * json: fix bounds tests * Update json-schema-to-grammar.cpp * json: fix negative max * json: fix negative min (w/ more than 1 digit) * Update test-grammar-integration.cpp * json: nit: move string rules together * json: port min/max integer support to Python & JS * nit: move + rename _build_min_max_int * fix min in [1, 9] * Update test-grammar-integration.cpp * add C++11-compatible replacement for std::string_view * add min/max constrained int field to pydantic json schema example * fix merge * json: add integration tests for min/max bounds * reshuffle/merge min/max integ test cases * nits / cleanups * defensive code against string out of bounds (apparently different behaviour of libstdc++ vs. clang's libc++, can't read final NULL char w/ former)
This commit is contained in:
		| @@ -40,6 +40,233 @@ static std::string build_repetition(const std::string & item_rule, int min_items | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Minimalistic replacement for std::string_view, which is only available from C++17 onwards */ | ||||||
|  | class string_view { | ||||||
|  |     const std::string & _str; | ||||||
|  |     const size_t _start; | ||||||
|  |     const size_t _end; | ||||||
|  | public: | ||||||
|  |     string_view(const std::string & str, size_t start = 0, size_t end  = std::string::npos) : _str(str), _start(start), _end(end == std::string::npos ? str.length() : end) {} | ||||||
|  |  | ||||||
|  |     size_t size() const { | ||||||
|  |         return _end - _start; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     size_t length() const { | ||||||
|  |         return size(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     operator std::string() const { | ||||||
|  |         return str(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::string str() const { | ||||||
|  |         return _str.substr(_start, _end - _start); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     string_view substr(size_t pos, size_t len = std::string::npos) const { | ||||||
|  |         return string_view(_str, _start + pos, len == std::string::npos ? _end : _start + pos + len); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     char operator[](size_t pos) const { | ||||||
|  |         auto index = _start + pos; | ||||||
|  |         if (index >= _end) { | ||||||
|  |             throw std::out_of_range("string_view index out of range"); | ||||||
|  |         } | ||||||
|  |         return _str[_start + pos]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool operator==(const string_view & other) const { | ||||||
|  |         std::string this_str = *this; | ||||||
|  |         std::string other_str = other; | ||||||
|  |         return this_str == other_str; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static void _build_min_max_int(int min_value, int max_value, std::stringstream & out, int decimals_left = 16, bool top_level = true) { | ||||||
|  |     auto has_min = min_value != std::numeric_limits<int>::min(); | ||||||
|  |     auto has_max = max_value != std::numeric_limits<int>::max(); | ||||||
|  |  | ||||||
|  |     auto digit_range = [&](char from, char to) { | ||||||
|  |         out << "["; | ||||||
|  |         if (from == to) { | ||||||
|  |             out << from; | ||||||
|  |         } else { | ||||||
|  |             out << from << "-" << to; | ||||||
|  |         } | ||||||
|  |         out << "]"; | ||||||
|  |     }; | ||||||
|  |     auto more_digits = [&](int min_digits, int max_digits) { | ||||||
|  |         out << "[0-9]"; | ||||||
|  |         if (min_digits == max_digits && min_digits == 1) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         out << "{"; | ||||||
|  |         out << min_digits; | ||||||
|  |         if (max_digits != min_digits) { | ||||||
|  |             out << ","; | ||||||
|  |             if (max_digits != std::numeric_limits<int>::max()) { | ||||||
|  |                 out << max_digits; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         out << "}"; | ||||||
|  |     }; | ||||||
|  |     std::function<void(const string_view &, const string_view &)> uniform_range = | ||||||
|  |         [&](const string_view & from, const string_view & to) { | ||||||
|  |             size_t i = 0; | ||||||
|  |             while (i < from.length() && i < to.length() && from[i] == to[i]) { | ||||||
|  |                 i++; | ||||||
|  |             } | ||||||
|  |             if (i > 0) { | ||||||
|  |                 out << "\"" << from.substr(0, i).str() << "\""; | ||||||
|  |             } | ||||||
|  |             if (i < from.length() && i < to.length()) { | ||||||
|  |                 if (i > 0) { | ||||||
|  |                     out << " "; | ||||||
|  |                 } | ||||||
|  |                 auto sub_len = from.length() - i - 1; | ||||||
|  |                 if (sub_len > 0) { | ||||||
|  |                     auto from_sub = from.substr(i + 1); | ||||||
|  |                     auto to_sub = to.substr(i + 1); | ||||||
|  |                     auto sub_zeros = repeat("0", sub_len); | ||||||
|  |                     auto sub_nines = repeat("9", sub_len); | ||||||
|  |  | ||||||
|  |                     auto to_reached = false; | ||||||
|  |                     out << "("; | ||||||
|  |                     if (from_sub == sub_zeros) { | ||||||
|  |                         digit_range(from[i], to[i] - 1); | ||||||
|  |                         out << " "; | ||||||
|  |                         more_digits(sub_len, sub_len); | ||||||
|  |                     } else { | ||||||
|  |                         out << "[" << from[i] << "] "; | ||||||
|  |                         out << "("; | ||||||
|  |                         uniform_range(from_sub, sub_nines); | ||||||
|  |                         out << ")"; | ||||||
|  |                         if (from[i] < to[i] - 1) { | ||||||
|  |                             out << " | "; | ||||||
|  |                             if (to_sub == sub_nines) { | ||||||
|  |                                 digit_range(from[i] + 1, to[i]); | ||||||
|  |                                 to_reached = true; | ||||||
|  |                             } else { | ||||||
|  |                                 digit_range(from[i] + 1, to[i] - 1); | ||||||
|  |                             } | ||||||
|  |                             out << " "; | ||||||
|  |                             more_digits(sub_len, sub_len); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     if (!to_reached) { | ||||||
|  |                         out << " | "; | ||||||
|  |                         digit_range(to[i], to[i]); | ||||||
|  |                         out << " "; | ||||||
|  |                         uniform_range(sub_zeros, to_sub); | ||||||
|  |                     } | ||||||
|  |                     out << ")"; | ||||||
|  |                 } else { | ||||||
|  |                     out << "[" << from[i] << "-" << to[i] << "]"; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |     if (has_min && has_max) { | ||||||
|  |         if (min_value < 0 && max_value < 0) { | ||||||
|  |             out << "\"-\" ("; | ||||||
|  |             _build_min_max_int(-max_value, -min_value, out, decimals_left, /* top_level= */ true); | ||||||
|  |             out << ")"; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (min_value < 0) { | ||||||
|  |             out << "\"-\" ("; | ||||||
|  |             _build_min_max_int(0, -min_value, out, decimals_left, /* top_level= */ true); | ||||||
|  |             out << ") | "; | ||||||
|  |             min_value = 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         auto min_s = std::to_string(min_value); | ||||||
|  |         auto max_s = std::to_string(max_value); | ||||||
|  |         auto min_digits = min_s.length(); | ||||||
|  |         auto max_digits = max_s.length(); | ||||||
|  |  | ||||||
|  |         for (auto digits = min_digits; digits < max_digits; digits++) { | ||||||
|  |             uniform_range(min_s, repeat("9", digits)); | ||||||
|  |             min_s = "1" + repeat("0", digits); | ||||||
|  |             out << " | "; | ||||||
|  |         } | ||||||
|  |         uniform_range(min_s, max_s); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auto less_decimals = std::max(decimals_left - 1, 1); | ||||||
|  |  | ||||||
|  |     if (has_min) { | ||||||
|  |         if (min_value < 0) { | ||||||
|  |             out << "\"-\" ("; | ||||||
|  |             _build_min_max_int(std::numeric_limits<int>::min(), -min_value, out, decimals_left, /* top_level= */ false); | ||||||
|  |             out << ") | [0] | [1-9] "; | ||||||
|  |             more_digits(0, decimals_left - 1); | ||||||
|  |         } else if (min_value == 0) { | ||||||
|  |             if (top_level) { | ||||||
|  |                 out << "[0] | [1-9] "; | ||||||
|  |                 more_digits(0, less_decimals); | ||||||
|  |             } else { | ||||||
|  |                 more_digits(1, decimals_left); | ||||||
|  |             } | ||||||
|  |         } else if (min_value <= 9) { | ||||||
|  |             char c = '0' + min_value; | ||||||
|  |             auto range_start = top_level ? '1' : '0'; | ||||||
|  |             if (c > range_start) { | ||||||
|  |                 digit_range(range_start, c - 1); | ||||||
|  |                 out << " "; | ||||||
|  |                 more_digits(1, less_decimals); | ||||||
|  |                 out << " | "; | ||||||
|  |             } | ||||||
|  |             digit_range(c, '9'); | ||||||
|  |             out << " "; | ||||||
|  |             more_digits(0, less_decimals); | ||||||
|  |         } else { | ||||||
|  |             auto min_s = std::to_string(min_value); | ||||||
|  |             auto len = min_s.length(); | ||||||
|  |             auto c = min_s[0]; | ||||||
|  |  | ||||||
|  |             if (c > '1') { | ||||||
|  |                 digit_range(top_level ? '1' : '0', c - 1); | ||||||
|  |                 out << " "; | ||||||
|  |                 more_digits(len, less_decimals); | ||||||
|  |                 out << " | "; | ||||||
|  |             } | ||||||
|  |             digit_range(c, c); | ||||||
|  |             out << " ("; | ||||||
|  |             _build_min_max_int(std::stoi(min_s.substr(1)), std::numeric_limits<int>::max(), out, less_decimals, /* top_level= */ false); | ||||||
|  |             out << ")"; | ||||||
|  |             if (c < '9') { | ||||||
|  |                 out << " | "; | ||||||
|  |                 digit_range(c + 1, '9'); | ||||||
|  |                 out << " "; | ||||||
|  |                 more_digits(len - 1, less_decimals); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (has_max) { | ||||||
|  |         if (max_value >= 0) { | ||||||
|  |             if (top_level) { | ||||||
|  |                 out << "\"-\" [1-9] "; | ||||||
|  |                 more_digits(0, less_decimals); | ||||||
|  |                 out << " | "; | ||||||
|  |             } | ||||||
|  |             _build_min_max_int(0, max_value, out, decimals_left, /* top_level= */ true); | ||||||
|  |         } else { | ||||||
|  |             out << "\"-\" ("; | ||||||
|  |             _build_min_max_int(-max_value, std::numeric_limits<int>::max(), out, decimals_left, /* top_level= */ false); | ||||||
|  |             out << ")"; | ||||||
|  |         } | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     throw std::runtime_error("At least one of min_value or max_value must be set"); | ||||||
|  | } | ||||||
|  |  | ||||||
| const std::string SPACE_RULE = "| \" \" | \"\\n\" [ \\t]{0,20}"; | const std::string SPACE_RULE = "| \" \" | \"\\n\" [ \\t]{0,20}"; | ||||||
|  |  | ||||||
| struct BuiltinRule { | struct BuiltinRule { | ||||||
| @@ -160,7 +387,6 @@ static std::string format_literal(const std::string & literal) { | |||||||
|     return "\"" + escaped + "\""; |     return "\"" + escaped + "\""; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class SchemaConverter { | class SchemaConverter { | ||||||
| private: | private: | ||||||
|     std::function<json(const std::string &)> _fetch_json; |     std::function<json(const std::string &)> _fetch_json; | ||||||
| @@ -686,6 +912,24 @@ public: | |||||||
|             int min_len = schema.contains("minLength") ? schema["minLength"].get<int>() : 0; |             int min_len = schema.contains("minLength") ? schema["minLength"].get<int>() : 0; | ||||||
|             int max_len = schema.contains("maxLength") ? schema["maxLength"].get<int>() : std::numeric_limits<int>::max(); |             int max_len = schema.contains("maxLength") ? schema["maxLength"].get<int>() : std::numeric_limits<int>::max(); | ||||||
|             return _add_rule(rule_name, "\"\\\"\" " + build_repetition(char_rule, min_len, max_len) + " \"\\\"\" space"); |             return _add_rule(rule_name, "\"\\\"\" " + build_repetition(char_rule, min_len, max_len) + " \"\\\"\" space"); | ||||||
|  |         } else if (schema_type == "integer" && (schema.contains("minimum") || schema.contains("exclusiveMinimum") || schema.contains("maximum") || schema.contains("exclusiveMaximum"))) { | ||||||
|  |             int min_value = std::numeric_limits<int>::min(); | ||||||
|  |             int max_value = std::numeric_limits<int>::max(); | ||||||
|  |             if (schema.contains("minimum")) { | ||||||
|  |                 min_value = schema["minimum"].get<int>(); | ||||||
|  |             } else if (schema.contains("exclusiveMinimum")) { | ||||||
|  |                 min_value = schema["exclusiveMinimum"].get<int>() + 1; | ||||||
|  |             } | ||||||
|  |             if (schema.contains("maximum")) { | ||||||
|  |                 max_value = schema["maximum"].get<int>(); | ||||||
|  |             } else if (schema.contains("exclusiveMaximum")) { | ||||||
|  |                 max_value = schema["exclusiveMaximum"].get<int>() - 1; | ||||||
|  |             } | ||||||
|  |             std::stringstream out; | ||||||
|  |             out << "("; | ||||||
|  |             _build_min_max_int(min_value, max_value, out); | ||||||
|  |             out << ") space"; | ||||||
|  |             return _add_rule(rule_name, out.str()); | ||||||
|         } else if (schema.empty() || schema_type == "object") { |         } else if (schema.empty() || schema_type == "object") { | ||||||
|             return _add_rule(rule_name, _add_primitive("object", PRIMITIVE_RULES.at("object"))); |             return _add_rule(rule_name, _add_primitive("object", PRIMITIVE_RULES.at("object"))); | ||||||
|         } else { |         } else { | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ if __name__ == '__main__': | |||||||
|         question: str |         question: str | ||||||
|         concise_answer: str |         concise_answer: str | ||||||
|         justification: str |         justification: str | ||||||
|  |         stars: Annotated[int, Field(ge=1, le=5)] | ||||||
|  |  | ||||||
|     class PyramidalSummary(BaseModel): |     class PyramidalSummary(BaseModel): | ||||||
|         title: str |         title: str | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import itertools | |||||||
| import json | import json | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
| from typing import Any, Dict, List, Set, Tuple, Union | from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union | ||||||
|  |  | ||||||
|  |  | ||||||
| def _build_repetition(item_rule, min_items, max_items, separator_rule=None): | def _build_repetition(item_rule, min_items, max_items, separator_rule=None): | ||||||
| @@ -23,6 +23,170 @@ def _build_repetition(item_rule, min_items, max_items, separator_rule=None): | |||||||
|     result = item_rule + ' ' + _build_repetition(f'({separator_rule} {item_rule})', min_items - 1 if min_items > 0 else 0, max_items - 1 if max_items is not None else None) |     result = item_rule + ' ' + _build_repetition(f'({separator_rule} {item_rule})', min_items - 1 if min_items > 0 else 0, max_items - 1 if max_items is not None else None) | ||||||
|     return f'({result})?' if min_items == 0 else result |     return f'({result})?' if min_items == 0 else result | ||||||
|  |  | ||||||
|  | def _generate_min_max_int(min_value: Optional[int], max_value: Optional[int], out: list, decimals_left: int = 16, top_level: bool = True): | ||||||
|  |     has_min = min_value != None | ||||||
|  |     has_max = max_value != None | ||||||
|  |  | ||||||
|  |     def digit_range(from_char: str, to_char: str): | ||||||
|  |         out.append("[") | ||||||
|  |         if from_char == to_char: | ||||||
|  |             out.append(from_char) | ||||||
|  |         else: | ||||||
|  |             out.append(from_char) | ||||||
|  |             out.append("-") | ||||||
|  |             out.append(to_char) | ||||||
|  |         out.append("]") | ||||||
|  |  | ||||||
|  |     def more_digits(min_digits: int, max_digits: int): | ||||||
|  |         out.append("[0-9]") | ||||||
|  |         if min_digits == max_digits and min_digits == 1: | ||||||
|  |             return | ||||||
|  |         out.append("{") | ||||||
|  |         out.append(str(min_digits)) | ||||||
|  |         if max_digits != min_digits: | ||||||
|  |             out.append(",") | ||||||
|  |             if max_digits != sys.maxsize: | ||||||
|  |                 out.append(str(max_digits)) | ||||||
|  |         out.append("}") | ||||||
|  |  | ||||||
|  |     def uniform_range(from_str: str, to_str: str): | ||||||
|  |         i = 0 | ||||||
|  |         while i < len(from_str) and from_str[i] == to_str[i]: | ||||||
|  |             i += 1 | ||||||
|  |         if i > 0: | ||||||
|  |             out.append("\"") | ||||||
|  |             out.append(from_str[:i]) | ||||||
|  |             out.append("\"") | ||||||
|  |         if i < len(from_str): | ||||||
|  |             if i > 0: | ||||||
|  |                 out.append(" ") | ||||||
|  |             sub_len = len(from_str) - i - 1 | ||||||
|  |             if sub_len > 0: | ||||||
|  |                 from_sub = from_str[i+1:] | ||||||
|  |                 to_sub = to_str[i+1:] | ||||||
|  |                 sub_zeros = "0" * sub_len | ||||||
|  |                 sub_nines = "9" * sub_len | ||||||
|  |  | ||||||
|  |                 to_reached = False | ||||||
|  |                 out.append("(") | ||||||
|  |                 if from_sub == sub_zeros: | ||||||
|  |                     digit_range(from_str[i], chr(ord(to_str[i]) - 1)) | ||||||
|  |                     out.append(" ") | ||||||
|  |                     more_digits(sub_len, sub_len) | ||||||
|  |                 else: | ||||||
|  |                     out.append("[") | ||||||
|  |                     out.append(from_str[i]) | ||||||
|  |                     out.append("] ") | ||||||
|  |                     out.append("(") | ||||||
|  |                     uniform_range(from_sub, sub_nines) | ||||||
|  |                     out.append(")") | ||||||
|  |                     if ord(from_str[i]) < ord(to_str[i]) - 1: | ||||||
|  |                         out.append(" | ") | ||||||
|  |                         if to_sub == sub_nines: | ||||||
|  |                             digit_range(chr(ord(from_str[i]) + 1), to_str[i]) | ||||||
|  |                             to_reached = True | ||||||
|  |                         else: | ||||||
|  |                             digit_range(chr(ord(from_str[i]) + 1), chr(ord(to_str[i]) - 1)) | ||||||
|  |                         out.append(" ") | ||||||
|  |                         more_digits(sub_len, sub_len) | ||||||
|  |                 if not to_reached: | ||||||
|  |                     out.append(" | ") | ||||||
|  |                     digit_range(to_str[i], to_str[i]) | ||||||
|  |                     out.append(" ") | ||||||
|  |                     uniform_range(sub_zeros, to_sub) | ||||||
|  |                 out.append(")") | ||||||
|  |             else: | ||||||
|  |                 out.append("[") | ||||||
|  |                 out.append(from_str[i]) | ||||||
|  |                 out.append("-") | ||||||
|  |                 out.append(to_str[i]) | ||||||
|  |                 out.append("]") | ||||||
|  |  | ||||||
|  |     if has_min and has_max: | ||||||
|  |         if min_value < 0 and max_value < 0: | ||||||
|  |             out.append("\"-\" (") | ||||||
|  |             _generate_min_max_int(-max_value, -min_value, out, decimals_left, top_level=True) | ||||||
|  |             out.append(")") | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if min_value < 0: | ||||||
|  |             out.append("\"-\" (") | ||||||
|  |             _generate_min_max_int(0, -min_value, out, decimals_left, top_level=True) | ||||||
|  |             out.append(") | ") | ||||||
|  |             min_value = 0 | ||||||
|  |  | ||||||
|  |         min_s = str(min_value) | ||||||
|  |         max_s = str(max_value) | ||||||
|  |         min_digits = len(min_s) | ||||||
|  |         max_digits = len(max_s) | ||||||
|  |  | ||||||
|  |         for digits in range(min_digits, max_digits): | ||||||
|  |             uniform_range(min_s, "9" * digits) | ||||||
|  |             min_s = "1" + "0" * digits | ||||||
|  |             out.append(" | ") | ||||||
|  |         uniform_range(min_s, max_s) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     less_decimals = max(decimals_left - 1, 1) | ||||||
|  |  | ||||||
|  |     if has_min: | ||||||
|  |         if min_value < 0: | ||||||
|  |             out.append("\"-\" (") | ||||||
|  |             _generate_min_max_int(None, -min_value, out, decimals_left, top_level=False) | ||||||
|  |             out.append(") | [0] | [1-9] ") | ||||||
|  |             more_digits(0, decimals_left - 1) | ||||||
|  |         elif min_value == 0: | ||||||
|  |             if top_level: | ||||||
|  |                 out.append("[0] | [1-9] ") | ||||||
|  |                 more_digits(0, less_decimals) | ||||||
|  |             else: | ||||||
|  |                 more_digits(1, decimals_left) | ||||||
|  |         elif min_value <= 9: | ||||||
|  |             c = str(min_value) | ||||||
|  |             range_start = '1' if top_level else '0' | ||||||
|  |             if c > range_start: | ||||||
|  |                 digit_range(range_start, chr(ord(c) - 1)) | ||||||
|  |                 out.append(" ") | ||||||
|  |                 more_digits(1, less_decimals) | ||||||
|  |                 out.append(" | ") | ||||||
|  |             digit_range(c, "9") | ||||||
|  |             out.append(" ") | ||||||
|  |             more_digits(0, less_decimals) | ||||||
|  |         else: | ||||||
|  |             min_s = str(min_value) | ||||||
|  |             length = len(min_s) | ||||||
|  |             c = min_s[0] | ||||||
|  |  | ||||||
|  |             if c > "1": | ||||||
|  |                 digit_range("1" if top_level else "0", chr(ord(c) - 1)) | ||||||
|  |                 out.append(" ") | ||||||
|  |                 more_digits(length, less_decimals) | ||||||
|  |                 out.append(" | ") | ||||||
|  |             digit_range(c, c) | ||||||
|  |             out.append(" (") | ||||||
|  |             _generate_min_max_int(int(min_s[1:]), None, out, less_decimals, top_level=False) | ||||||
|  |             out.append(")") | ||||||
|  |             if c < "9": | ||||||
|  |                 out.append(" | ") | ||||||
|  |                 digit_range(chr(ord(c) + 1), "9") | ||||||
|  |                 out.append(" ") | ||||||
|  |                 more_digits(length - 1, less_decimals) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     if has_max: | ||||||
|  |         if max_value >= 0: | ||||||
|  |             if top_level: | ||||||
|  |                 out.append("\"-\" [1-9] ") | ||||||
|  |                 more_digits(0, less_decimals) | ||||||
|  |                 out.append(" | ") | ||||||
|  |             _generate_min_max_int(0, max_value, out, decimals_left, top_level=True) | ||||||
|  |         else: | ||||||
|  |             out.append("\"-\" (") | ||||||
|  |             _generate_min_max_int(-max_value, None, out, decimals_left, top_level=False) | ||||||
|  |             out.append(")") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     raise RuntimeError("At least one of min_value or max_value must be set") | ||||||
|  |  | ||||||
| class BuiltinRule: | class BuiltinRule: | ||||||
|     def __init__(self, content: str, deps: list = None): |     def __init__(self, content: str, deps: list = None): | ||||||
| @@ -432,6 +596,24 @@ class SchemaConverter: | |||||||
|  |  | ||||||
|             return self._add_rule(rule_name, r'"\"" ' + _build_repetition(char_rule, min_len, max_len) + r' "\"" space') |             return self._add_rule(rule_name, r'"\"" ' + _build_repetition(char_rule, min_len, max_len) + r' "\"" space') | ||||||
|  |  | ||||||
|  |         elif schema_type in (None, 'integer') and \ | ||||||
|  |                 ('minimum' in schema or 'exclusiveMinimum' in schema or 'maximum' in schema or 'exclusiveMaximum' in schema): | ||||||
|  |             min_value = None | ||||||
|  |             max_value = None | ||||||
|  |             if 'minimum' in schema: | ||||||
|  |                 min_value = schema['minimum'] | ||||||
|  |             elif 'exclusiveMinimum' in schema: | ||||||
|  |                 min_value = schema['exclusiveMinimum'] + 1 | ||||||
|  |             if 'maximum' in schema: | ||||||
|  |                 max_value = schema['maximum'] | ||||||
|  |             elif 'exclusiveMaximum' in schema: | ||||||
|  |                 max_value = schema['exclusiveMaximum'] - 1 | ||||||
|  |  | ||||||
|  |             out = ["("] | ||||||
|  |             _generate_min_max_int(min_value, max_value, out) | ||||||
|  |             out.append(") space") | ||||||
|  |             return self._add_rule(rule_name, ''.join(out)) | ||||||
|  |  | ||||||
|         elif (schema_type == 'object') or (len(schema) == 0): |         elif (schema_type == 'object') or (len(schema) == 0): | ||||||
|             return self._add_rule(rule_name, self._add_primitive('object', PRIMITIVE_RULES['object'])) |             return self._add_rule(rule_name, self._add_primitive('object', PRIMITIVE_RULES['object'])) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,6 +24,201 @@ function _buildRepetition(itemRule, minItems, maxItems, opts={}) { | |||||||
|   return minItems === 0 ? `(${result})?` : result; |   return minItems === 0 ? `(${result})?` : result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function _generateMinMaxInt(minValue, maxValue, out, decimalsLeft = 16, topLevel = true) { | ||||||
|  |   const hasMin = minValue !== null; | ||||||
|  |   const hasMax = maxValue !== null; | ||||||
|  |  | ||||||
|  |   function digitRange(fromChar, toChar) { | ||||||
|  |       out.push("["); | ||||||
|  |       if (fromChar === toChar) { | ||||||
|  |           out.push(fromChar); | ||||||
|  |       } else { | ||||||
|  |           out.push(fromChar); | ||||||
|  |           out.push("-"); | ||||||
|  |           out.push(toChar); | ||||||
|  |       } | ||||||
|  |       out.push("]"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   function moreDigits(minDigits, maxDigits) { | ||||||
|  |       out.push("[0-9]"); | ||||||
|  |       if (minDigits === maxDigits && minDigits === 1) { | ||||||
|  |           return; | ||||||
|  |       } | ||||||
|  |       out.push("{"); | ||||||
|  |       out.push(minDigits.toString()); | ||||||
|  |       if (maxDigits !== minDigits) { | ||||||
|  |           out.push(","); | ||||||
|  |           if (maxDigits !== Number.MAX_SAFE_INTEGER) { | ||||||
|  |               out.push(maxDigits.toString()); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |       out.push("}"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   function uniformRange(fromStr, toStr) { | ||||||
|  |       let i = 0; | ||||||
|  |       while (i < fromStr.length && fromStr[i] === toStr[i]) { | ||||||
|  |           i++; | ||||||
|  |       } | ||||||
|  |       if (i > 0) { | ||||||
|  |           out.push("\""); | ||||||
|  |           out.push(fromStr.slice(0, i)); | ||||||
|  |           out.push("\""); | ||||||
|  |       } | ||||||
|  |       if (i < fromStr.length) { | ||||||
|  |           if (i > 0) { | ||||||
|  |               out.push(" "); | ||||||
|  |           } | ||||||
|  |           const subLen = fromStr.length - i - 1; | ||||||
|  |           if (subLen > 0) { | ||||||
|  |               const fromSub = fromStr.slice(i + 1); | ||||||
|  |               const toSub = toStr.slice(i + 1); | ||||||
|  |               const subZeros = "0".repeat(subLen); | ||||||
|  |               const subNines = "9".repeat(subLen); | ||||||
|  |  | ||||||
|  |               let toReached = false; | ||||||
|  |               out.push("("); | ||||||
|  |               if (fromSub === subZeros) { | ||||||
|  |                   digitRange(fromStr[i], String.fromCharCode(toStr.charCodeAt(i) - 1)); | ||||||
|  |                   out.push(" "); | ||||||
|  |                   moreDigits(subLen, subLen); | ||||||
|  |               } else { | ||||||
|  |                   out.push("["); | ||||||
|  |                   out.push(fromStr[i]); | ||||||
|  |                   out.push("] "); | ||||||
|  |                   out.push("("); | ||||||
|  |                   uniformRange(fromSub, subNines); | ||||||
|  |                   out.push(")"); | ||||||
|  |                   if (fromStr.charCodeAt(i) < toStr.charCodeAt(i) - 1) { | ||||||
|  |                       out.push(" | "); | ||||||
|  |                       if (toSub === subNines) { | ||||||
|  |                           digitRange(String.fromCharCode(fromStr.charCodeAt(i) + 1), toStr[i]); | ||||||
|  |                           toReached = true; | ||||||
|  |                       } else { | ||||||
|  |                           digitRange(String.fromCharCode(fromStr.charCodeAt(i) + 1), String.fromCharCode(toStr.charCodeAt(i) - 1)); | ||||||
|  |                       } | ||||||
|  |                       out.push(" "); | ||||||
|  |                       moreDigits(subLen, subLen); | ||||||
|  |                   } | ||||||
|  |               } | ||||||
|  |               if (!toReached) { | ||||||
|  |                   out.push(" | "); | ||||||
|  |                   digitRange(toStr[i], toStr[i]); | ||||||
|  |                   out.push(" "); | ||||||
|  |                   uniformRange(subZeros, toSub); | ||||||
|  |               } | ||||||
|  |               out.push(")"); | ||||||
|  |           } else { | ||||||
|  |               out.push("["); | ||||||
|  |               out.push(fromStr[i]); | ||||||
|  |               out.push("-"); | ||||||
|  |               out.push(toStr[i]); | ||||||
|  |               out.push("]"); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (hasMin && hasMax) { | ||||||
|  |       if (minValue < 0 && maxValue < 0) { | ||||||
|  |           out.push("\"-\" ("); | ||||||
|  |           _generateMinMaxInt(-maxValue, -minValue, out, decimalsLeft, true); | ||||||
|  |           out.push(")"); | ||||||
|  |           return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (minValue < 0) { | ||||||
|  |           out.push("\"-\" ("); | ||||||
|  |           _generateMinMaxInt(0, -minValue, out, decimalsLeft, true); | ||||||
|  |           out.push(") | "); | ||||||
|  |           minValue = 0; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       let minS = minValue.toString(); | ||||||
|  |       const maxS = maxValue.toString(); | ||||||
|  |       const minDigits = minS.length; | ||||||
|  |       const maxDigits = maxS.length; | ||||||
|  |  | ||||||
|  |       for (let digits = minDigits; digits < maxDigits; digits++) { | ||||||
|  |           uniformRange(minS, "9".repeat(digits)); | ||||||
|  |           minS = "1" + "0".repeat(digits); | ||||||
|  |           out.push(" | "); | ||||||
|  |       } | ||||||
|  |       uniformRange(minS, maxS); | ||||||
|  |       return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const lessDecimals = Math.max(decimalsLeft - 1, 1); | ||||||
|  |  | ||||||
|  |   if (hasMin) { | ||||||
|  |       if (minValue < 0) { | ||||||
|  |           out.push("\"-\" ("); | ||||||
|  |           _generateMinMaxInt(null, -minValue, out, decimalsLeft, false); | ||||||
|  |           out.push(") | [0] | [1-9] "); | ||||||
|  |           moreDigits(0, decimalsLeft - 1); | ||||||
|  |       } else if (minValue === 0) { | ||||||
|  |           if (topLevel) { | ||||||
|  |               out.push("[0] | [1-9] "); | ||||||
|  |               moreDigits(0, lessDecimals); | ||||||
|  |           } else { | ||||||
|  |               moreDigits(1, decimalsLeft); | ||||||
|  |           } | ||||||
|  |       } else if (minValue <= 9) { | ||||||
|  |           const c = minValue.toString(); | ||||||
|  |           const range_start = topLevel ? '1' : '0'; | ||||||
|  |           if (c > range_start) { | ||||||
|  |               digitRange(range_start, String.fromCharCode(c.charCodeAt(0) - 1)); | ||||||
|  |               out.push(" "); | ||||||
|  |               moreDigits(1, lessDecimals); | ||||||
|  |               out.push(" | "); | ||||||
|  |           } | ||||||
|  |           digitRange(c, "9"); | ||||||
|  |           out.push(" "); | ||||||
|  |           moreDigits(0, lessDecimals); | ||||||
|  |       } else { | ||||||
|  |           const minS = minValue.toString(); | ||||||
|  |           const length = minS.length; | ||||||
|  |           const c = minS[0]; | ||||||
|  |  | ||||||
|  |           if (c > "1") { | ||||||
|  |               digitRange(topLevel ? "1" : "0", String.fromCharCode(c.charCodeAt(0) - 1)); | ||||||
|  |               out.push(" "); | ||||||
|  |               moreDigits(length, lessDecimals); | ||||||
|  |               out.push(" | "); | ||||||
|  |           } | ||||||
|  |           digitRange(c, c); | ||||||
|  |           out.push(" ("); | ||||||
|  |           _generateMinMaxInt(parseInt(minS.slice(1)), null, out, lessDecimals, false); | ||||||
|  |           out.push(")"); | ||||||
|  |           if (c < "9") { | ||||||
|  |               out.push(" | "); | ||||||
|  |               digitRange(String.fromCharCode(c.charCodeAt(0) + 1), "9"); | ||||||
|  |               out.push(" "); | ||||||
|  |               moreDigits(length - 1, lessDecimals); | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (hasMax) { | ||||||
|  |       if (maxValue >= 0) { | ||||||
|  |           if (topLevel) { | ||||||
|  |               out.push("\"-\" [1-9] "); | ||||||
|  |               moreDigits(0, lessDecimals); | ||||||
|  |               out.push(" | "); | ||||||
|  |           } | ||||||
|  |           _generateMinMaxInt(0, maxValue, out, decimalsLeft, true); | ||||||
|  |       } else { | ||||||
|  |           out.push("\"-\" ("); | ||||||
|  |           _generateMinMaxInt(-maxValue, null, out, decimalsLeft, false); | ||||||
|  |           out.push(")"); | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   throw new Error("At least one of minValue or maxValue must be set"); | ||||||
|  | } | ||||||
|  |  | ||||||
| class BuiltinRule { | class BuiltinRule { | ||||||
|   constructor(content, deps) { |   constructor(content, deps) { | ||||||
|     this.content = content; |     this.content = content; | ||||||
| @@ -435,6 +630,24 @@ export class SchemaConverter { | |||||||
|       const minLen = schema.minLength || 0; |       const minLen = schema.minLength || 0; | ||||||
|       const maxLen = schema.maxLength; |       const maxLen = schema.maxLength; | ||||||
|       return this._addRule(ruleName, '"\\\"" ' + _buildRepetition(charRuleName, minLen, maxLen) + ' "\\\"" space'); |       return this._addRule(ruleName, '"\\\"" ' + _buildRepetition(charRuleName, minLen, maxLen) + ' "\\\"" space'); | ||||||
|  |     } else if (schemaType === 'integer' && ('minimum' in schema || 'exclusiveMinimum' in schema || 'maximum' in schema || 'exclusiveMaximum' in schema)) { | ||||||
|  |       let minValue = null; | ||||||
|  |       let maxValue = null; | ||||||
|  |       if ('minimum' in schema) { | ||||||
|  |         minValue = schema.minimum; | ||||||
|  |       } else if ('exclusiveMinimum' in schema) { | ||||||
|  |         minValue = schema.exclusiveMinimum + 1; | ||||||
|  |       } | ||||||
|  |       if ('maximum' in schema) { | ||||||
|  |         maxValue = schema.maximum; | ||||||
|  |       } else if ('exclusiveMaximum' in schema) { | ||||||
|  |         maxValue = schema.exclusiveMaximum - 1; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       const out = ["("]; | ||||||
|  |       _generateMinMaxInt(minValue, maxValue, out); | ||||||
|  |       out.push(") space"); | ||||||
|  |       return this._addRule(ruleName, out.join('')); | ||||||
|     } else if ((schemaType === 'object') || (Object.keys(schema).length === 0)) { |     } else if ((schemaType === 'object') || (Object.keys(schema).length === 0)) { | ||||||
|       return this._addRule(ruleName, this._addPrimitive('object', PRIMITIVE_RULES['object'])); |       return this._addRule(ruleName, this._addPrimitive('object', PRIMITIVE_RULES['object'])); | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -148,6 +148,250 @@ static void test_schema(const std::string & test_desc, const std::string & schem | |||||||
| } | } | ||||||
|  |  | ||||||
| static void test_simple_grammar() { | static void test_simple_grammar() { | ||||||
|  |     test_schema( | ||||||
|  |         "min 0", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 0 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "0", | ||||||
|  |             "10", | ||||||
|  |             "12", | ||||||
|  |             "10000", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "-1", | ||||||
|  |             "-10", | ||||||
|  |             "-10000", | ||||||
|  |             "-100000000000000000000000000000000", | ||||||
|  |             "100000000000000000000000000000000", | ||||||
|  |             "00", | ||||||
|  |             "01", | ||||||
|  |             "-0", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "min 2", | ||||||
|  |         // Schema | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 2 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "2", | ||||||
|  |             "3", | ||||||
|  |             "4", | ||||||
|  |             "10", | ||||||
|  |             "20", | ||||||
|  |             "1234567890000000", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "0", | ||||||
|  |             "1", | ||||||
|  |             "-1", | ||||||
|  |             "-100", | ||||||
|  |             "0", | ||||||
|  |             "1", | ||||||
|  |             "01", | ||||||
|  |             "02", | ||||||
|  |             "12345678900000000", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "min 456", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 456 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "456", | ||||||
|  |             "4560", | ||||||
|  |             "457", | ||||||
|  |             "460", | ||||||
|  |             "500", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "455", | ||||||
|  |             "356", | ||||||
|  |             "50", | ||||||
|  |             "050", | ||||||
|  |             "-1", | ||||||
|  |             "-456", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "min -123", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -123 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "-123", | ||||||
|  |             "-122", | ||||||
|  |             "-11", | ||||||
|  |             "-1", | ||||||
|  |             "0", | ||||||
|  |             "1", | ||||||
|  |             "123", | ||||||
|  |             "1234", | ||||||
|  |             "2345", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "-1234", | ||||||
|  |             "-124", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     test_schema( | ||||||
|  |         "max 9999", | ||||||
|  |         // Schema | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "maximum": 9999 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "-99999", | ||||||
|  |             "0", | ||||||
|  |             "9999", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "10000", | ||||||
|  |             "99991", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "max -9999", | ||||||
|  |         // Schema | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "maximum": -9999 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "-10000", | ||||||
|  |             "-9999", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "-9998", | ||||||
|  |             "0", | ||||||
|  |             "9999", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "min 5 max 30", | ||||||
|  |         // Schema | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 5, | ||||||
|  |             "maximum": 30 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "5", | ||||||
|  |             "10", | ||||||
|  |             "30", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "05", | ||||||
|  |             "4", | ||||||
|  |             "-1", | ||||||
|  |             "31", | ||||||
|  |             "123", | ||||||
|  |             "0123", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "min -1 max 1", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -1, | ||||||
|  |             "maximum": 1 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "-1", | ||||||
|  |             "0", | ||||||
|  |             "1", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "-11", | ||||||
|  |             "-10", | ||||||
|  |             "-2", | ||||||
|  |             "2", | ||||||
|  |             "10", | ||||||
|  |             "11", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "min -123 max 42", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -123, | ||||||
|  |             "maximum": 42 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "-123", | ||||||
|  |             "-122", | ||||||
|  |             "-13", | ||||||
|  |             "-11", | ||||||
|  |             "-2", | ||||||
|  |             "-1", | ||||||
|  |             "0", | ||||||
|  |             "1", | ||||||
|  |             "5", | ||||||
|  |             "10", | ||||||
|  |             "39", | ||||||
|  |             "40", | ||||||
|  |             "42", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "-0123", | ||||||
|  |             "-124", | ||||||
|  |             "-1123", | ||||||
|  |             "-200", | ||||||
|  |             "43", | ||||||
|  |             "123", | ||||||
|  |             "0123", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |     test_schema( | ||||||
|  |         "exclusive min / max", | ||||||
|  |         // Schema | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "exclusiveMinimum": 0, | ||||||
|  |             "exclusiveMaximum": 10000 | ||||||
|  |         })""", | ||||||
|  |         // Passing strings | ||||||
|  |         { | ||||||
|  |             "1", | ||||||
|  |             "9999", | ||||||
|  |         }, | ||||||
|  |         // Failing strings | ||||||
|  |         { | ||||||
|  |             "0", | ||||||
|  |             "01", | ||||||
|  |             "10000", | ||||||
|  |             "99999", | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     // Test case for a simple grammar |     // Test case for a simple grammar | ||||||
|     test_grammar( |     test_grammar( | ||||||
|         "simple grammar", |         "simple grammar", | ||||||
| @@ -773,7 +1017,6 @@ static void test_json_schema() { | |||||||
|         } |         } | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |  | ||||||
|     test_schema( |     test_schema( | ||||||
|         "min+max items", |         "min+max items", | ||||||
|         // Schema |         // Schema | ||||||
|   | |||||||
| @@ -80,6 +80,232 @@ static void test_all(const std::string & lang, std::function<void(const TestCase | |||||||
|         runner(tc); |         runner(tc); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 0", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 0 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([0] | [1-9] [0-9]{0,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 1", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 1 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([1-9] [0-9]{0,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 3", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 3 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([1-2] [0-9]{1,15} | [3-9] [0-9]{0,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 9", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 9 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([1-8] [0-9]{1,15} | [9] [0-9]{0,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 10", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 10 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([1] ([0-9]{1,15}) | [2-9] [0-9]{1,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 25", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 25 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([1] [0-9]{2,15} | [2] ([0-4] [0-9]{1,14} | [5-9] [0-9]{0,14}) | [3-9] [0-9]{1,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "max 30", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "maximum": 30 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" [1-9] [0-9]{0,15} | [0-9] | ([1-2] [0-9] | [3] "0")) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min -5", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -5 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" ([0-5]) | [0] | [1-9] [0-9]{0,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min -123", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -123 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" ([0-9] | ([1-8] [0-9] | [9] [0-9]) | "1" ([0-1] [0-9] | [2] [0-3])) | [0] | [1-9] [0-9]{0,15}) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "max -5", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "maximum": -5 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" ([0-4] [0-9]{1,15} | [5-9] [0-9]{0,15})) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "max 1", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "maximum": 1 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" [1-9] [0-9]{0,15} | [0-1]) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "max 100", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "maximum": 100 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" [1-9] [0-9]{0,15} | [0-9] | ([1-8] [0-9] | [9] [0-9]) | "100") space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 0 max 23", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 0, | ||||||
|  |             "maximum": 23 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([0-9] | ([1] [0-9] | [2] [0-3])) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 15 max 300", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 15, | ||||||
|  |             "maximum": 300 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= (([1] ([5-9]) | [2-9] [0-9]) | ([1-2] [0-9]{2} | [3] "00")) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min 5 max 30", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": 5, | ||||||
|  |             "maximum": 30 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ([5-9] | ([1-2] [0-9] | [3] "0")) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min -123 max 42", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -123, | ||||||
|  |             "maximum": 42 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" ([0-9] | ([1-8] [0-9] | [9] [0-9]) | "1" ([0-1] [0-9] | [2] [0-3])) | [0-9] | ([1-3] [0-9] | [4] [0-2])) space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min -10 max 10", | ||||||
|  |         R"""({ | ||||||
|  |             "type": "integer", | ||||||
|  |             "minimum": -10, | ||||||
|  |             "maximum": 10 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             root ::= ("-" ([0-9] | "10") | [0-9] | "10") space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     test({ |     test({ | ||||||
|         FAILURE, |         FAILURE, | ||||||
|         "unknown type", |         "unknown type", | ||||||
| @@ -390,6 +616,44 @@ static void test_all(const std::string & lang, std::function<void(const TestCase | |||||||
|         )""" |         )""" | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min + max items with min + max values across zero", | ||||||
|  |         R"""({ | ||||||
|  |             "items": { | ||||||
|  |                 "type": "integer", | ||||||
|  |                 "minimum": -12, | ||||||
|  |                 "maximum": 207 | ||||||
|  |             }, | ||||||
|  |             "minItems": 3, | ||||||
|  |             "maxItems": 5 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             item ::= ("-" ([0-9] | "1" [0-2]) | [0-9] | ([1-8] [0-9] | [9] [0-9]) | ([1] [0-9]{2} | [2] "0" [0-7])) space | ||||||
|  |             root ::= "[" space item ("," space item){2,4} "]" space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test({ | ||||||
|  |         SUCCESS, | ||||||
|  |         "min + max items with min + max values", | ||||||
|  |         R"""({ | ||||||
|  |             "items": { | ||||||
|  |                 "type": "integer", | ||||||
|  |                 "minimum": 12, | ||||||
|  |                 "maximum": 207 | ||||||
|  |             }, | ||||||
|  |             "minItems": 3, | ||||||
|  |             "maxItems": 5 | ||||||
|  |         })""", | ||||||
|  |         R"""( | ||||||
|  |             item ::= (([1] ([2-9]) | [2-9] [0-9]) | ([1] [0-9]{2} | [2] "0" [0-7])) space | ||||||
|  |             root ::= "[" space item ("," space item){2,4} "]" space | ||||||
|  |             space ::= | " " | "\n" [ \t]{0,20} | ||||||
|  |         )""" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     test({ |     test({ | ||||||
|         SUCCESS, |         SUCCESS, | ||||||
|         "simple regexp", |         "simple regexp", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Olivier Chafik
					Olivier Chafik