1111
1212using json = nlohmann::ordered_json;
1313
14- template <typename Iterator>
15- static std::string join (Iterator begin, Iterator end, const std::string & separator);
16-
1714static std::string repeat (const std::string & str, size_t n);
1815
1916static std::string build_repetition (const std::string & item_rule, int min_items, int max_items, const std::string & separator_rule = " " ) {
@@ -397,22 +394,6 @@ class SchemaConverter {
397394 std::vector<std::string> _errors;
398395 std::vector<std::string> _warnings;
399396
400- std::string _add_rule (const std::string & name, const std::string & rule) {
401- std::string esc_name = regex_replace (name, INVALID_RULE_CHARS_RE, " -" );
402- if (_rules.find (esc_name) == _rules.end () || _rules[esc_name] == rule) {
403- _rules[esc_name] = rule;
404- return esc_name;
405- } else {
406- int i = 0 ;
407- while (_rules.find (esc_name + std::to_string (i)) != _rules.end () && _rules[esc_name + std::to_string (i)] != rule) {
408- i++;
409- }
410- std::string key = esc_name + std::to_string (i);
411- _rules[key] = rule;
412- return key;
413- }
414- }
415-
416397 std::string _generate_union_rule (const std::string & name, const std::vector<json> & alt_schemas) {
417398 std::vector<std::string> rules;
418399 for (size_t i = 0 ; i < alt_schemas.size (); i++) {
@@ -449,7 +430,7 @@ class SchemaConverter {
449430 } else {
450431 rule = " [^\\ x0A\\ x0D]" ;
451432 }
452- return _add_rule (" dot" , rule);
433+ return add_rule (" dot" , rule);
453434 };
454435
455436 // Joins the sequence, merging consecutive literals together.
@@ -566,7 +547,7 @@ class SchemaConverter {
566547 if (!sub_is_literal) {
567548 std::string & sub_id = sub_rule_ids[sub];
568549 if (sub_id.empty ()) {
569- sub_id = _add_rule (name + " -" + std::to_string (sub_rule_ids.size ()), sub);
550+ sub_id = add_rule (name + " -" + std::to_string (sub_rule_ids.size ()), sub);
570551 }
571552 sub = sub_id;
572553 }
@@ -611,7 +592,7 @@ class SchemaConverter {
611592 }
612593 return join_seq ();
613594 };
614- return _add_rule (name, " \"\\\"\" " + to_rule (transform ()) + " \"\\\"\" space" );
595+ return add_rule (name, " \"\\\"\" " + to_rule (transform ()) + " \"\\\"\" space" );
615596 }
616597
617598 /*
@@ -709,7 +690,7 @@ class SchemaConverter {
709690 const auto &prop_schema = kv.second ;
710691
711692 std::string prop_rule_name = visit (prop_schema, name + (name.empty () ? " " : " -" ) + prop_name);
712- prop_kv_rule_names[prop_name] = _add_rule (
693+ prop_kv_rule_names[prop_name] = add_rule (
713694 name + (name.empty () ? " " : " -" ) + prop_name + " -kv" ,
714695 format_literal (json (prop_name).dump ()) + " space \" :\" space " + prop_rule_name
715696 );
@@ -728,8 +709,8 @@ class SchemaConverter {
728709
729710 auto key_rule =
730711 prop_names.empty () ? _add_primitive (" string" , PRIMITIVE_RULES.at (" string" ))
731- : _add_rule (sub_name + " -k" , _not_strings (prop_names));
732- std::string kv_rule = _add_rule (sub_name + " -kv" , key_rule + " \" :\" space " + value_rule);
712+ : add_rule (sub_name + " -k" , _not_strings (prop_names));
713+ std::string kv_rule = add_rule (sub_name + " -kv" , key_rule + " \" :\" space " + value_rule);
733714 prop_kv_rule_names[" *" ] = kv_rule;
734715 optional_props.push_back (" *" );
735716 }
@@ -762,7 +743,7 @@ class SchemaConverter {
762743 res = kv_rule_name + (k == " *" ? " " + comma_ref + " *" : " " );
763744 }
764745 if (ks.size () > 1 ) {
765- res += " " + _add_rule (
746+ res += " " + add_rule (
766747 name + (name.empty () ? " " : " -" ) + k + " -rest" ,
767748 get_recursive_refs (std::vector<std::string>(ks.begin () + 1 , ks.end ()), true )
768749 );
@@ -788,7 +769,7 @@ class SchemaConverter {
788769 }
789770
790771 std::string _add_primitive (const std::string & name, const BuiltinRule & rule) {
791- auto n = _add_rule (name, rule.content );
772+ auto n = add_rule (name, rule.content );
792773 for (const auto & dep : rule.deps ) {
793774 BuiltinRule dep_rule;
794775 auto it = PRIMITIVE_RULES.find (dep);
@@ -815,6 +796,22 @@ class SchemaConverter {
815796 _rules[" space" ] = SPACE_RULE;
816797 }
817798
799+ std::string add_rule (const std::string & name, const std::string & rule) {
800+ std::string esc_name = regex_replace (name, INVALID_RULE_CHARS_RE, " -" );
801+ if (_rules.find (esc_name) == _rules.end () || _rules[esc_name] == rule) {
802+ _rules[esc_name] = rule;
803+ return esc_name;
804+ } else {
805+ int i = 0 ;
806+ while (_rules.find (esc_name + std::to_string (i)) != _rules.end () && _rules[esc_name + std::to_string (i)] != rule) {
807+ i++;
808+ }
809+ std::string key = esc_name + std::to_string (i);
810+ _rules[key] = rule;
811+ return key;
812+ }
813+ }
814+
818815 void resolve_refs (json & schema, const std::string & url) {
819816 /*
820817 * Resolves all $ref fields in the given schema, fetching any remote schemas,
@@ -886,26 +883,26 @@ class SchemaConverter {
886883 std::string rule_name = is_reserved_name (name) ? name + " -" : name.empty () ? " root" : name;
887884
888885 if (schema.contains (" $ref" )) {
889- return _add_rule (rule_name, _resolve_ref (schema[" $ref" ]));
886+ return add_rule (rule_name, _resolve_ref (schema[" $ref" ]));
890887 } else if (schema.contains (" oneOf" ) || schema.contains (" anyOf" )) {
891888 std::vector<json> alt_schemas = schema.contains (" oneOf" ) ? schema[" oneOf" ].get <std::vector<json>>() : schema[" anyOf" ].get <std::vector<json>>();
892- return _add_rule (rule_name, _generate_union_rule (name, alt_schemas));
889+ return add_rule (rule_name, _generate_union_rule (name, alt_schemas));
893890 } else if (schema_type.is_array ()) {
894891 std::vector<json> schema_types;
895892 for (const auto & t : schema_type) {
896893 json schema_copy (schema);
897894 schema_copy[" type" ] = t;
898895 schema_types.push_back (schema_copy);
899896 }
900- return _add_rule (rule_name, _generate_union_rule (name, schema_types));
897+ return add_rule (rule_name, _generate_union_rule (name, schema_types));
901898 } else if (schema.contains (" const" )) {
902- return _add_rule (rule_name, _generate_constant_rule (schema[" const" ]) + " space" );
899+ return add_rule (rule_name, _generate_constant_rule (schema[" const" ]) + " space" );
903900 } else if (schema.contains (" enum" )) {
904901 std::vector<std::string> enum_values;
905902 for (const auto & v : schema[" enum" ]) {
906903 enum_values.push_back (_generate_constant_rule (v));
907904 }
908- return _add_rule (rule_name, " (" + join (enum_values.begin (), enum_values.end (), " | " ) + " ) space" );
905+ return add_rule (rule_name, " (" + join (enum_values.begin (), enum_values.end (), " | " ) + " ) space" );
909906 } else if ((schema_type.is_null () || schema_type == " object" )
910907 && (schema.contains (" properties" ) ||
911908 (schema.contains (" additionalProperties" ) && schema[" additionalProperties" ] != true ))) {
@@ -923,7 +920,7 @@ class SchemaConverter {
923920 properties.emplace_back (prop.key (), prop.value ());
924921 }
925922 }
926- return _add_rule (rule_name,
923+ return add_rule (rule_name,
927924 _build_object_rule (
928925 properties, required, name,
929926 schema.contains (" additionalProperties" ) ? schema[" additionalProperties" ] : json ()));
@@ -954,7 +951,7 @@ class SchemaConverter {
954951 add_component (t, true );
955952 }
956953 }
957- return _add_rule (rule_name, _build_object_rule (properties, required, hybrid_name, json ()));
954+ return add_rule (rule_name, _build_object_rule (properties, required, hybrid_name, json ()));
958955 } else if ((schema_type.is_null () || schema_type == " array" ) && (schema.contains (" items" ) || schema.contains (" prefixItems" ))) {
959956 json items = schema.contains (" items" ) ? schema[" items" ] : schema[" prefixItems" ];
960957 if (items.is_array ()) {
@@ -966,27 +963,27 @@ class SchemaConverter {
966963 rule += visit (items[i], name + (name.empty () ? " " : " -" ) + " tuple-" + std::to_string (i));
967964 }
968965 rule += " \" ]\" space" ;
969- return _add_rule (rule_name, rule);
966+ return add_rule (rule_name, rule);
970967 } else {
971968 std::string item_rule_name = visit (items, name + (name.empty () ? " " : " -" ) + " item" );
972969 int min_items = schema.contains (" minItems" ) ? schema[" minItems" ].get <int >() : 0 ;
973970 json max_items_json = schema.contains (" maxItems" ) ? schema[" maxItems" ] : json ();
974971 int max_items = max_items_json.is_number_integer () ? max_items_json.get <int >() : std::numeric_limits<int >::max ();
975972
976- return _add_rule (rule_name, " \" [\" space " + build_repetition (item_rule_name, min_items, max_items, " \" ,\" space" ) + " \" ]\" space" );
973+ return add_rule (rule_name, " \" [\" space " + build_repetition (item_rule_name, min_items, max_items, " \" ,\" space" ) + " \" ]\" space" );
977974 }
978975 } else if ((schema_type.is_null () || schema_type == " string" ) && schema.contains (" pattern" )) {
979976 return _visit_pattern (schema[" pattern" ], rule_name);
980977 } else if ((schema_type.is_null () || schema_type == " string" ) && std::regex_match (schema_format, std::regex (" ^uuid[1-5]?$" ))) {
981978 return _add_primitive (rule_name == " root" ? " root" : schema_format, PRIMITIVE_RULES.at (" uuid" ));
982979 } else if ((schema_type.is_null () || schema_type == " string" ) && STRING_FORMAT_RULES.find (schema_format + " -string" ) != STRING_FORMAT_RULES.end ()) {
983980 auto prim_name = schema_format + " -string" ;
984- return _add_rule (rule_name, _add_primitive (prim_name, STRING_FORMAT_RULES.at (prim_name)));
981+ return add_rule (rule_name, _add_primitive (prim_name, STRING_FORMAT_RULES.at (prim_name)));
985982 } else if (schema_type == " string" && (schema.contains (" minLength" ) || schema.contains (" maxLength" ))) {
986983 std::string char_rule = _add_primitive (" char" , PRIMITIVE_RULES.at (" char" ));
987984 int min_len = schema.contains (" minLength" ) ? schema[" minLength" ].get <int >() : 0 ;
988985 int max_len = schema.contains (" maxLength" ) ? schema[" maxLength" ].get <int >() : std::numeric_limits<int >::max ();
989- return _add_rule (rule_name, " \"\\\"\" " + build_repetition (char_rule, min_len, max_len) + " \"\\\"\" space" );
986+ return add_rule (rule_name, " \"\\\"\" " + build_repetition (char_rule, min_len, max_len) + " \"\\\"\" space" );
990987 } else if (schema_type == " integer" && (schema.contains (" minimum" ) || schema.contains (" exclusiveMinimum" ) || schema.contains (" maximum" ) || schema.contains (" exclusiveMaximum" ))) {
991988 int min_value = std::numeric_limits<int >::min ();
992989 int max_value = std::numeric_limits<int >::max ();
@@ -1004,9 +1001,9 @@ class SchemaConverter {
10041001 out << " (" ;
10051002 _build_min_max_int (min_value, max_value, out);
10061003 out << " ) space" ;
1007- return _add_rule (rule_name, out.str ());
1004+ return add_rule (rule_name, out.str ());
10081005 } else if (schema.empty () || schema_type == " object" ) {
1009- return _add_rule (rule_name, _add_primitive (" object" , PRIMITIVE_RULES.at (" object" )));
1006+ return add_rule (rule_name, _add_primitive (" object" , PRIMITIVE_RULES.at (" object" )));
10101007 } else {
10111008 if (!schema_type.is_string () || PRIMITIVE_RULES.find (schema_type.get <std::string>()) == PRIMITIVE_RULES.end ()) {
10121009 _errors.push_back (" Unrecognized schema: " + schema.dump ());
@@ -1036,10 +1033,28 @@ class SchemaConverter {
10361033};
10371034
10381035std::string json_schema_to_grammar (const json & schema) {
1039- SchemaConverter converter ([](const std::string &) { return json::object (); }, /* dotall= */ false );
1040- auto copy = schema;
1041- converter.resolve_refs (copy, " input" );
1042- converter.visit (copy, " " );
1036+ return build_grammar ([&](const llama_grammar_builder & callbacks) {
1037+ auto copy = schema;
1038+ callbacks.resolve_refs (copy);
1039+ callbacks.add_schema (" root" , copy);
1040+ });
1041+ }
1042+
1043+ std::string build_grammar (const std::function<void (const llama_grammar_builder &)> & cb) {
1044+ SchemaConverter converter ([&](const std::string & name) { return json (); }, /* dotall= */ false );
1045+ llama_grammar_builder builder {
1046+ .add_rule = [&](const std::string & name, const std::string & rule) {
1047+ return converter.add_rule (name, rule);
1048+ },
1049+ .add_schema = [&](const std::string & name, const nlohmann::ordered_json & schema) {
1050+ return converter.visit (schema, name);
1051+ },
1052+ .resolve_refs = [&](nlohmann::ordered_json & schema) {
1053+ converter.resolve_refs (schema, " " );
1054+ }
1055+ };
1056+ cb (builder);
10431057 converter.check_errors ();
10441058 return converter.format_grammar ();
10451059}
1060+
0 commit comments