2024-03-21 11:50:43 +00:00
#ifdef NDEBUG
#undef NDEBUG
#endif
2024-09-07 15:16:19 +03:00
#include "json-schema-to-grammar.h"
2025-04-24 16:00:10 +03:00
#include "../src/llama-grammar.h"
2024-09-07 15:16:19 +03:00
2025-05-30 16:25:45 +03:00
#include <nlohmann/json.hpp>
2024-05-08 21:53:08 +02:00
#include <cassert>
2024-03-21 11:50:43 +00:00
#include <fstream>
#include <sstream>
#include <regex>
static std :: string trim ( const std :: string & source ) {
std :: string s ( source );
s . erase ( 0 , s . find_first_not_of ( " \n\r\t " ));
s . erase ( s . find_last_not_of ( " \n\r\t " ) + 1 );
return std :: regex_replace ( s , std :: regex ( "(^| \n )[ \t ]+" ), "$1" );
}
enum TestCaseStatus {
SUCCESS ,
FAILURE
};
struct TestCase {
TestCaseStatus expected_status ;
std :: string name ;
std :: string schema ;
std :: string expected_grammar ;
void _print_failure_header () const {
fprintf ( stderr , "# \n # Test '%s' failed. \n # \n %s \n " , name . c_str (), schema . c_str ());
}
void verify ( const std :: string & actual_grammar ) const {
if ( trim ( actual_grammar ) != trim ( expected_grammar )) {
_print_failure_header ();
fprintf ( stderr , "# EXPECTED: \n %s \n # ACTUAL: \n %s \n " , expected_grammar . c_str (), actual_grammar . c_str ());
assert ( false );
}
}
void verify_expectation_parseable () const {
try {
2024-09-07 15:16:19 +03:00
llama_grammar_parser state ;
state . parse ( expected_grammar . c_str ());
2024-03-21 11:50:43 +00:00
if ( state . symbol_ids . find ( "root" ) == state . symbol_ids . end ()) {
throw std :: runtime_error ( "Grammar failed to parse: \n " + expected_grammar );
}
} catch ( const std :: runtime_error & ex ) {
_print_failure_header ();
fprintf ( stderr , "# GRAMMAR ERROR: %s \n " , ex . what ());
assert ( false );
}
}
void verify_status ( TestCaseStatus status ) const {
if ( status != expected_status ) {
_print_failure_header ();
fprintf ( stderr , "# EXPECTED STATUS: %s \n " , expected_status == SUCCESS ? "SUCCESS" : "FAILURE" );
fprintf ( stderr , "# ACTUAL STATUS: %s \n " , status == SUCCESS ? "SUCCESS" : "FAILURE" );
assert ( false );
}
}
};
static void write ( const std :: string & file , const std :: string & content ) {
std :: ofstream f ;
f . open ( file . c_str ());
f << content . c_str ();
f . close ();
}
static std :: string read ( const std :: string & file ) {
std :: ostringstream actuals ;
actuals << std :: ifstream ( file . c_str ()). rdbuf ();
return actuals . str ();
}
static void test_all ( const std :: string & lang , std :: function < void ( const TestCase & ) > runner ) {
fprintf ( stderr , "# \n # Testing JSON schema conversion (%s) \n # \n " , lang . c_str ());
auto test = [ & ]( const TestCase & tc ) {
fprintf ( stderr , "- %s%s \n " , tc . name . c_str (), tc . expected_status == FAILURE ? " (failure expected)" : "" );
runner ( tc );
};
2024-06-25 20:06:20 +01:00
test ({
SUCCESS ,
"min 0" ,
R """({
"type" : "integer" ,
"minimum" : 0
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 1" ,
R """({
"type" : "integer" ,
"minimum" : 1
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 3" ,
R """({
"type" : "integer" ,
"minimum" : 3
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 1 - 2 ] [ 0 - 9 ]{ 1 , 15 } | [ 3 - 9 ] [ 0 - 9 ]{ 0 , 15 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 9" ,
R """({
"type" : "integer" ,
"minimum" : 9
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 1 - 8 ] [ 0 - 9 ]{ 1 , 15 } | [ 9 ] [ 0 - 9 ]{ 0 , 15 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 10" ,
R """({
"type" : "integer" ,
"minimum" : 10
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 1 ] ([ 0 - 9 ]{ 1 , 15 }) | [ 2 - 9 ] [ 0 - 9 ]{ 1 , 15 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 25" ,
R """({
"type" : "integer" ,
"minimum" : 25
}) """,
R """(
2026-06-20 17:43:04 -05:00
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 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"max 30" ,
R """({
"type" : "integer" ,
"maximum" : 30
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "-" [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 } | [ 0 - 9 ] | ([ 1 - 2 ] [ 0 - 9 ] | [ 3 ] "0" ))
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min -5" ,
R """({
"type" : "integer" ,
"minimum" : - 5
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "-" ([ 0 - 5 ]) | [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min -123" ,
R """({
"type" : "integer" ,
"minimum" : - 123
}) """,
R """(
2026-06-20 17:43:04 -05:00
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 })
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"max -5" ,
R """({
"type" : "integer" ,
"maximum" : - 5
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "-" ([ 0 - 4 ] [ 0 - 9 ]{ 1 , 15 } | [ 5 - 9 ] [ 0 - 9 ]{ 0 , 15 }))
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"max 1" ,
R """({
"type" : "integer" ,
"maximum" : 1
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "-" [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 } | [ 0 - 1 ])
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"max 100" ,
R """({
"type" : "integer" ,
"maximum" : 100
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "-" [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 } | [ 0 - 9 ] | ([ 1 - 8 ] [ 0 - 9 ] | [ 9 ] [ 0 - 9 ]) | "100" )
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 0 max 23" ,
R """({
"type" : "integer" ,
"minimum" : 0 ,
"maximum" : 23
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 0 - 9 ] | ([ 1 ] [ 0 - 9 ] | [ 2 ] [ 0 - 3 ]))
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 15 max 300" ,
R """({
"type" : "integer" ,
"minimum" : 15 ,
"maximum" : 300
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= (([ 1 ] ([ 5 - 9 ]) | [ 2 - 9 ] [ 0 - 9 ]) | ([ 1 - 2 ] [ 0 - 9 ]{ 2 } | [ 3 ] "00" ))
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min 5 max 30" ,
R """({
"type" : "integer" ,
"minimum" : 5 ,
"maximum" : 30
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ([ 5 - 9 ] | ([ 1 - 2 ] [ 0 - 9 ] | [ 3 ] "0" ))
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min -123 max 42" ,
R """({
"type" : "integer" ,
"minimum" : - 123 ,
"maximum" : 42
}) """,
R """(
2026-06-20 17:43:04 -05:00
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 ]))
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min -10 max 10" ,
R """({
"type" : "integer" ,
"minimum" : - 10 ,
"maximum" : 10
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "-" ([ 0 - 9 ] | "10" ) | [ 0 - 9 ] | "10" )
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
2024-03-21 11:50:43 +00:00
test ({
FAILURE ,
"unknown type" ,
R """({
"type" : "kaboom"
}) """,
""
});
test ({
FAILURE ,
2024-03-22 13:07:44 +00:00
"invalid type" ,
2024-03-21 11:50:43 +00:00
R """({
"type" : 123
}) """,
""
});
test ({
SUCCESS ,
"empty schema (object)" ,
"{}" ,
R """(
2026-06-20 17:43:04 -05:00
array ::= "[" space ( value ( "," space value ) * ) ? space "]"
boolean ::= ( "true" | "false" )
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
null ::= "null"
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
object ::= "{" space ( string ":" space value ( "," space string ":" space value ) * ) ? space "}"
2024-03-21 11:50:43 +00:00
root ::= object
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-04-12 19:43:38 +01:00
value ::= object | array | string | number | boolean | null
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"exotic formats" ,
R """({
"items" : [
{ "format" : "date" },
{ "format" : "uuid" },
{ "format" : "time" },
{ "format" : "date-time" }
]
}) """,
R """(
2024-06-06 10:07:06 +01:00
date ::= [ 0 - 9 ]{ 4 } "-" ( "0" [ 1 - 9 ] | "1" [ 0 - 2 ] ) "-" ( "0" [ 1 - 9 ] | [ 1 - 2 ] [ 0 - 9 ] | "3" [ 0 - 1 ] )
2026-06-20 17:43:04 -05:00
date - string ::= " \" " date " \" "
2024-03-21 11:50:43 +00:00
date - time ::= date "T" time
2026-06-20 17:43:04 -05:00
date - time - string ::= " \" " date - time " \" "
root ::= "[" space tuple - 0 "," space uuid "," space tuple - 2 "," space tuple - 3 space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-06 10:07:06 +01:00
time ::= ([ 01 ] [ 0 - 9 ] | "2" [ 0 - 3 ]) ":" [ 0 - 5 ] [ 0 - 9 ] ":" [ 0 - 5 ] [ 0 - 9 ] ( "." [ 0 - 9 ]{ 3 } ) ? ( "Z" | ( "+" | "-" ) ( [ 01 ] [ 0 - 9 ] | "2" [ 0 - 3 ] ) ":" [ 0 - 5 ] [ 0 - 9 ] )
2026-06-20 17:43:04 -05:00
time - string ::= " \" " time " \" "
2024-04-12 19:43:38 +01:00
tuple - 0 ::= date - string
tuple - 2 ::= time - string
tuple - 3 ::= date - time - string
2026-06-20 17:43:04 -05:00
uuid ::= " \" " [ 0 - 9 a - fA - F ]{ 8 } "-" [ 0 - 9 a - fA - F ]{ 4 } "-" [ 0 - 9 a - fA - F ]{ 4 } "-" [ 0 - 9 a - fA - F ]{ 4 } "-" [ 0 - 9 a - fA - F ]{ 12 } " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"string" ,
R """({
"type" : "string"
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= " \" " char * " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-04-12 19:43:38 +01:00
) """
});
test ({
SUCCESS ,
"string w/ min length 1" ,
R """({
"type" : "string" ,
"minLength" : 1
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= " \" " char + " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-04-12 19:43:38 +01:00
) """
});
test ({
SUCCESS ,
"string w/ min length 3" ,
R """({
"type" : "string" ,
"minLength" : 3
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= " \" " char { 3 ,} " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-04-12 19:43:38 +01:00
) """
});
test ({
SUCCESS ,
"string w/ max length" ,
R """({
"type" : "string" ,
"maxLength" : 3
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= " \" " char { 0 , 3 } " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-04-12 19:43:38 +01:00
) """
});
test ({
SUCCESS ,
"string w/ min & max length" ,
R """({
"type" : "string" ,
"minLength" : 1 ,
"maxLength" : 4
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= " \" " char { 1 , 4 } " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"boolean" ,
R """({
"type" : "boolean"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( "true" | "false" )
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"integer" ,
R """({
"type" : "integer"
}) """,
R """(
2024-06-06 10:07:06 +01:00
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
root ::= ( "-" ? integral - part )
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"string const" ,
R """({
"const" : "foo"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" foo \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
2024-03-22 13:07:44 +00:00
SUCCESS ,
2024-03-21 11:50:43 +00:00
"non-string const" ,
R """({
"const" : 123
}) """,
2024-03-22 13:07:44 +00:00
R """(
2026-06-20 17:43:04 -05:00
root ::= "123"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-22 13:07:44 +00:00
) """
2024-03-21 11:50:43 +00:00
});
test ({
2024-03-22 13:07:44 +00:00
SUCCESS ,
2024-03-21 11:50:43 +00:00
"non-string enum" ,
R """({
2024-03-22 13:07:44 +00:00
"enum" : [ "red" , "amber" , "green" , null , 42 , [ "foo" ]]
2024-03-21 11:50:43 +00:00
}) """,
2024-03-22 13:07:44 +00:00
R """(
2026-06-20 17:43:04 -05:00
root ::= ( " \" red \" " | " \" amber \" " | " \" green \" " | "null" | "42" | "[ \" foo \" ]" )
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-22 13:07:44 +00:00
) """
2024-03-21 11:50:43 +00:00
});
2024-06-26 01:46:35 +01:00
test ({
SUCCESS ,
"string array" ,
R """({
"type" : "array" ,
"prefixItems" : { "type" : "string" }
}) """,
R """(
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= "[" space ( string ( "," space string ) * ) ? space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-06-26 01:46:35 +01:00
) """
});
test ({
SUCCESS ,
"nullable string array" ,
R """({
"type" : [ "array" , "null" ],
"prefixItems" : { "type" : "string" }
}) """,
R """(
2026-06-20 17:43:04 -05:00
alternative - 0 ::= "[" space ( string ( "," space string ) * ) ? space "]"
2024-06-26 01:46:35 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
null ::= "null"
2024-06-26 01:46:35 +01:00
root ::= alternative - 0 | null
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-06-26 01:46:35 +01:00
) """
});
2024-03-21 11:50:43 +00:00
test ({
SUCCESS ,
"tuple1" ,
R """({
"prefixItems" : [{ "type" : "string" }]
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= "[" space string space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"tuple2" ,
R """({
"prefixItems" : [{ "type" : "string" }, { "type" : "number" }]
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "[" space string "," space number space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
2026-03-10 21:38:18 +08:00
test ({
SUCCESS ,
"array with empty items" ,
R """({
"type" : "array" ,
"items" : {}
}) """,
R """(
2026-06-20 17:43:04 -05:00
array ::= "[" space ( value ( "," space value ) * ) ? space "]"
boolean ::= ( "true" | "false" )
2026-03-10 21:38:18 +08:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
item ::= object
2026-06-20 17:43:04 -05:00
null ::= "null"
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
object ::= "{" space ( string ":" space value ( "," space string ":" space value ) * ) ? space "}"
root ::= "[" space ( item ( "," space item ) * ) ? space "]"
2026-03-10 21:38:18 +08:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2026-03-10 21:38:18 +08:00
value ::= object | array | string | number | boolean | null
) """
});
test ({
SUCCESS ,
"array with empty items and prefixItems" ,
R """({
"type" : "array" ,
"items" : {},
"prefixItems" : { "type" : "string" }
}) """,
R """(
2026-06-20 17:43:04 -05:00
array ::= "[" space ( value ( "," space value ) * ) ? space "]"
boolean ::= ( "true" | "false" )
2026-03-10 21:38:18 +08:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
item ::= object
2026-06-20 17:43:04 -05:00
null ::= "null"
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
object ::= "{" space ( string ":" space value ( "," space string ":" space value ) * ) ? space "}"
root ::= "[" space ( item ( "," space item ) * ) ? space "]"
2026-03-10 21:38:18 +08:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2026-03-10 21:38:18 +08:00
value ::= object | array | string | number | boolean | null
) """
});
2024-03-21 11:50:43 +00:00
test ({
SUCCESS ,
"number" ,
R """({
"type" : "number"
}) """,
R """(
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
root ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"minItems" ,
R """({
"items" : {
"type" : "boolean"
},
"minItems" : 2
}) """,
R """(
2026-06-20 17:43:04 -05:00
boolean ::= ( "true" | "false" )
root ::= "[" space boolean ( "," space boolean ) + space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
2025-04-26 10:10:20 +02:00
test ({
SUCCESS ,
"maxItems 0" ,
R """({
"items" : {
"type" : "boolean"
},
"maxItems" : 0
}) """,
R """(
2026-06-20 17:43:04 -05:00
boolean ::= ( "true" | "false" )
root ::= "[" space space "]"
2025-04-26 10:10:20 +02:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
) """
});
2024-03-21 11:50:43 +00:00
test ({
SUCCESS ,
"maxItems 1" ,
R """({
"items" : {
"type" : "boolean"
},
"maxItems" : 1
}) """,
R """(
2026-06-20 17:43:04 -05:00
boolean ::= ( "true" | "false" )
root ::= "[" space boolean ? space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"maxItems 2" ,
R """({
"items" : {
"type" : "boolean"
},
"maxItems" : 2
}) """,
R """(
2026-06-20 17:43:04 -05:00
boolean ::= ( "true" | "false" )
root ::= "[" space ( boolean ( "," space boolean ) ? ) ? space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"min + maxItems" ,
R """({
"items" : {
"type" : [ "number" , "integer" ]
},
"minItems" : 3 ,
"maxItems" : 5
}) """,
R """(
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
2026-06-20 17:43:04 -05:00
integer ::= ( "-" ? integral - part )
2024-06-06 10:07:06 +01:00
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2024-03-21 11:50:43 +00:00
item ::= number | integer
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "[" space item ( "," space item ){ 2 , 4 } space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
2024-06-25 20:06:20 +01:00
test ({
SUCCESS ,
"min + max items with min + max values across zero" ,
R """({
"items" : {
"type" : "integer" ,
"minimum" : - 12 ,
"maximum" : 207
},
"minItems" : 3 ,
"maxItems" : 5
}) """,
R """(
2026-06-20 17:43:04 -05:00
item ::= ( "-" ([ 0 - 9 ] | "1" [ 0 - 2 ]) | [ 0 - 9 ] | ([ 1 - 8 ] [ 0 - 9 ] | [ 9 ] [ 0 - 9 ]) | ([ 1 ] [ 0 - 9 ]{ 2 } | [ 2 ] "0" [ 0 - 7 ]))
root ::= "[" space item ( "," space item ){ 2 , 4 } space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
test ({
SUCCESS ,
"min + max items with min + max values" ,
R """({
"items" : {
"type" : "integer" ,
"minimum" : 12 ,
"maximum" : 207
},
"minItems" : 3 ,
"maxItems" : 5
}) """,
R """(
2026-06-20 17:43:04 -05:00
item ::= (([ 1 ] ([ 2 - 9 ]) | [ 2 - 9 ] [ 0 - 9 ]) | ([ 1 ] [ 0 - 9 ]{ 2 } | [ 2 ] "0" [ 0 - 7 ]))
root ::= "[" space item ( "," space item ){ 2 , 4 } space "]"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-25 20:06:20 +01:00
) """
});
2024-03-21 11:50:43 +00:00
test ({
SUCCESS ,
"simple regexp" ,
R """({
"type" : "string" ,
"pattern" : "^abc?d*efg+(hij)?kl$"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" " ( "ab" "c" ? "d" * "ef" "g" + ( "hij" ) ? "kl" ) " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"regexp escapes" ,
R """({
"type" : "string" ,
"pattern" : "^ \\ [ \\ ] \\ { \\ } \\ ( \\ ) \\ | \\ + \\ * \\ ?$"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" " ( "[]{}()|+*?" ) " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"regexp quote" ,
R """({
"type" : "string" ,
"pattern" : "^ \" $"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" " ( " \" " ) " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-10-16 09:03:24 -07:00
) """
});
test ({
SUCCESS ,
"regexp with top-level alternation" ,
R """({
"type" : "string" ,
"pattern" : "^A|B|C|D$"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" " ( "A" | "B" | "C" | "D" ) " \" "
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"regexp" ,
R """({
"type" : "string" ,
2024-04-12 19:43:38 +01:00
"pattern" : "^( \\ ([0-9]{1,3} \\ ))?[0-9]{3}-[0-9]{4} a{3,5}nd...$"
2024-03-21 11:50:43 +00:00
}) """,
R """(
2024-04-12 19:43:38 +01:00
dot ::= [ ^ \ x0A \ x0D ]
2026-06-20 17:43:04 -05:00
root ::= " \" " (( "(" root - 1 { 1 , 3 } ")" ) ? root - 1 { 3 , 3 } "-" root - 1 { 4 , 4 } " " "a" { 3 , 5 } "nd" dot dot dot ) " \" "
2024-03-21 11:50:43 +00:00
root - 1 ::= [ 0 - 9 ]
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
2024-03-22 13:07:44 +00:00
"required props in original order" ,
2024-03-21 11:50:43 +00:00
R """({
"type" : "object" ,
"properties" : {
2024-03-22 13:07:44 +00:00
"b" : { "type" : "string" },
"c" : { "type" : "string" },
"a" : { "type" : "string" }
2024-03-21 11:50:43 +00:00
},
"required" : [
"a" ,
2024-03-22 13:07:44 +00:00
"b" ,
"c"
2024-03-21 11:50:43 +00:00
],
"additionalProperties" : false ,
"definitions" : {}
}) """,
R """(
a - kv ::= " \" a \" " space ":" space string
b - kv ::= " \" b \" " space ":" space string
2024-03-22 13:07:44 +00:00
c - kv ::= " \" c \" " space ":" space string
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= "{" space b - kv "," space c - kv "," space a - kv space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"1 optional prop" ,
R """({
"properties" : {
"a" : {
"type" : "string"
}
},
"additionalProperties" : false
}) """,
R """(
a - kv ::= " \" a \" " space ":" space string
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= "{" space ( a - kv ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"N optional props" ,
R """({
"properties" : {
"a" : { "type" : "string" },
"b" : { "type" : "string" },
"c" : { "type" : "string" }
},
"additionalProperties" : false
}) """,
R """(
a - kv ::= " \" a \" " space ":" space string
a - rest ::= ( "," space b - kv ) ? b - rest
b - kv ::= " \" b \" " space ":" space string
b - rest ::= ( "," space c - kv ) ?
c - kv ::= " \" c \" " space ":" space string
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
root ::= "{" space ( a - kv a - rest | b - kv b - rest | c - kv ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
2024-03-22 13:07:44 +00:00
"required + optional props each in original order" ,
2024-03-21 11:50:43 +00:00
R """({
"properties" : {
"b" : { "type" : "string" },
2024-03-22 13:07:44 +00:00
"a" : { "type" : "string" },
"d" : { "type" : "string" },
"c" : { "type" : "string" }
2024-03-21 11:50:43 +00:00
},
"required" : [ "a" , "b" ],
"additionalProperties" : false
}) """,
R """(
a - kv ::= " \" a \" " space ":" space string
b - kv ::= " \" b \" " space ":" space string
c - kv ::= " \" c \" " space ":" space string
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-03-21 11:50:43 +00:00
d - kv ::= " \" d \" " space ":" space string
2024-03-22 13:07:44 +00:00
d - rest ::= ( "," space c - kv ) ?
2026-06-20 17:43:04 -05:00
root ::= "{" space b - kv "," space a - kv ( "," space ( d - kv d - rest | c - kv ) ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"additional props" ,
R """({
"type" : "object" ,
"additionalProperties" : { "type" : "array" , "items" : { "type" : "number" }}
}) """,
R """(
additional - kv ::= string ":" space additional - value
2026-06-20 17:43:04 -05:00
additional - value ::= "[" space ( number ( "," space number ) * ) ? space "]"
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "{" space ( additional - kv ( "," space additional - kv ) * ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"additional props (true)" ,
R """({
"type" : "object" ,
"additionalProperties" : true
}) """,
R """(
2026-06-20 17:43:04 -05:00
array ::= "[" space ( value ( "," space value ) * ) ? space "]"
boolean ::= ( "true" | "false" )
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
null ::= "null"
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
object ::= "{" space ( string ":" space value ( "," space string ":" space value ) * ) ? space "}"
2024-03-21 11:50:43 +00:00
root ::= object
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-04-12 19:43:38 +01:00
value ::= object | array | string | number | boolean | null
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"additional props (implicit)" ,
R """({
"type" : "object"
}) """,
R """(
2026-06-20 17:43:04 -05:00
array ::= "[" space ( value ( "," space value ) * ) ? space "]"
boolean ::= ( "true" | "false" )
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
null ::= "null"
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
object ::= "{" space ( string ":" space value ( "," space string ":" space value ) * ) ? space "}"
2024-03-21 11:50:43 +00:00
root ::= object
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-04-12 19:43:38 +01:00
value ::= object | array | string | number | boolean | null
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"empty w/o additional props" ,
R """({
"type" : "object" ,
"additionalProperties" : false
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= "{" space space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"required + additional props" ,
R """({
"type" : "object" ,
"properties" : {
"a" : { "type" : "number" }
},
"required" : [ "a" ],
"additionalProperties" : { "type" : "string" }
}) """,
R """(
a - kv ::= " \" a \" " space ":" space number
2026-06-20 17:43:04 -05:00
additional - k ::= [ "] ( [a] char+ | [^" a ] char * ) ? [ "]
2024-06-26 01:45:58 +01:00
additional - kv ::= additional - k ":" space string
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "{" space a - kv ( "," space ( additional - kv ( "," space additional - kv ) * ) ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"optional + additional props" ,
R """({
"type" : "object" ,
"properties" : {
"a" : { "type" : "number" }
},
"additionalProperties" : { "type" : "number" }
}) """,
R """(
a - kv ::= " \" a \" " space ":" space number
2024-06-26 01:45:58 +01:00
a - rest ::= ( "," space additional - kv ) *
2026-06-20 17:43:04 -05:00
additional - k ::= [ "] ( [a] char+ | [^" a ] char * ) ? [ "]
2024-06-26 01:45:58 +01:00
additional - kv ::= additional - k ":" space number
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "{" space ( a - kv a - rest | additional - kv ( "," space additional - kv ) * ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"required + optional + additional props" ,
R """({
"type" : "object" ,
"properties" : {
2024-06-26 01:45:58 +01:00
"and" : { "type" : "number" },
"also" : { "type" : "number" }
2024-03-21 11:50:43 +00:00
},
2024-06-26 01:45:58 +01:00
"required" : [ "and" ],
2024-03-21 11:50:43 +00:00
"additionalProperties" : { "type" : "number" }
}) """,
R """(
2026-06-20 17:43:04 -05:00
additional - k ::= [ "] ( [a] ([l] ([s] ([o] char+ | [^" o ] char * ) | [ ^ "s] char*) | [n] ([d] char+ | [^" d ] char * ) | [ ^ "ln] char*) | [^" a ] char * ) ? [ "]
2024-06-26 01:45:58 +01:00
additional - kv ::= additional - k ":" space number
also - kv ::= " \" also \" " space ":" space number
also - rest ::= ( "," space additional - kv ) *
and - kv ::= " \" and \" " space ":" space number
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "{" space and - kv ( "," space ( also - kv also - rest | additional - kv ( "," space additional - kv ) * ) ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-26 01:45:58 +01:00
) """
});
test ({
SUCCESS ,
"optional props with empty name" ,
R """({
"properties" : {
"" : { "type" : "integer" },
"a" : { "type" : "integer" }
},
"additionalProperties" : { "type" : "integer" }
}) """,
R """(
- kv ::= " \"\" " space ":" space root
- rest ::= ( "," space a - kv ) ? a - rest
a - kv ::= " \" a \" " space ":" space integer
a - rest ::= ( "," space additional - kv ) *
2026-06-20 17:43:04 -05:00
additional - k ::= [ "] ( [a] char+ | [^" a ] char * ) [ "]
2024-06-26 01:45:58 +01:00
additional - kv ::= additional - k ":" space integer
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
integer ::= ( "-" ? integral - part )
2024-06-26 01:45:58 +01:00
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
root ::= ( "-" ? integral - part )
root0 ::= "{" space ( - kv - rest | a - kv a - rest | additional - kv ( "," space additional - kv ) * ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-26 01:45:58 +01:00
) """
});
test ({
SUCCESS ,
"optional props with nested names" ,
R """({
"properties" : {
"a" : { "type" : "integer" },
"aa" : { "type" : "integer" }
},
"additionalProperties" : { "type" : "integer" }
}) """,
R """(
a - kv ::= " \" a \" " space ":" space integer
a - rest ::= ( "," space aa - kv ) ? aa - rest
aa - kv ::= " \" aa \" " space ":" space integer
aa - rest ::= ( "," space additional - kv ) *
2026-06-20 17:43:04 -05:00
additional - k ::= [ "] ( [a] ([a] char+ | [^" a ] char * ) | [ ^ "a] char* )? [" ]
2024-06-26 01:45:58 +01:00
additional - kv ::= additional - k ":" space integer
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
integer ::= ( "-" ? integral - part )
2024-06-26 01:45:58 +01:00
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
root ::= "{" space ( a - kv a - rest | aa - kv aa - rest | additional - kv ( "," space additional - kv ) * ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-06-26 01:45:58 +01:00
) """
});
test ({
SUCCESS ,
"optional props with common prefix" ,
R """({
"properties" : {
"ab" : { "type" : "integer" },
"ac" : { "type" : "integer" }
},
"additionalProperties" : { "type" : "integer" }
}) """,
R """(
ab - kv ::= " \" ab \" " space ":" space integer
ab - rest ::= ( "," space ac - kv ) ? ac - rest
ac - kv ::= " \" ac \" " space ":" space integer
ac - rest ::= ( "," space additional - kv ) *
2026-06-20 17:43:04 -05:00
additional - k ::= [ "] ( [a] ([b] char+ | [c] char+ | [^" bc ] char * ) | [ ^ "a] char* )? [" ]
2024-06-26 01:45:58 +01:00
additional - kv ::= additional - k ":" space integer
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
integer ::= ( "-" ? integral - part )
2024-06-26 01:45:58 +01:00
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
root ::= "{" space ( ab - kv ab - rest | ac - kv ac - rest | additional - kv ( "," space additional - kv ) * ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"top-level $ref" ,
R """({
2024-04-12 19:43:38 +01:00
"$ref" : "#/definitions/foo" ,
2024-03-21 11:50:43 +00:00
"definitions" : {
2024-04-12 19:43:38 +01:00
"foo" : {
2024-03-22 13:07:44 +00:00
"type" : "object" ,
"properties" : {
"a" : {
"type" : "string"
}
},
"required" : [
"a"
],
"additionalProperties" : false
2024-03-21 11:50:43 +00:00
}
}
}) """,
R """(
2024-06-11 01:00:30 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
2026-06-20 17:43:04 -05:00
ref - definitions - foo ::= "{" space ref - definitions - foo - a - kv space "}"
2025-10-28 03:37:52 -05:00
ref - definitions - foo - a - kv ::= " \" a \" " space ":" space string
root ::= ref - definitions - foo
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2024-03-21 11:50:43 +00:00
) """
});
test ({
SUCCESS ,
"anyOf" ,
R """({
"anyOf" : [
{ "$ref" : "#/definitions/foo" },
{ "$ref" : "#/definitions/bar" }
],
"definitions" : {
"foo" : {
2024-03-22 13:07:44 +00:00
"properties" : { "a" : { "type" : "number" }}
2024-03-21 11:50:43 +00:00
},
"bar" : {
2024-03-22 13:07:44 +00:00
"properties" : { "b" : { "type" : "number" }}
2024-03-21 11:50:43 +00:00
}
},
"type" : "object"
}) """,
R """(
2025-10-28 03:37:52 -05:00
alternative - 0 ::= ref - definitions - foo
alternative - 1 ::= ref - definitions - bar
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
ref - definitions - bar ::= "{" space ( ref - definitions - bar - b - kv ) ? space "}"
2025-10-28 03:37:52 -05:00
ref - definitions - bar - b - kv ::= " \" b \" " space ":" space number
2026-06-20 17:43:04 -05:00
ref - definitions - foo ::= "{" space ( ref - definitions - foo - a - kv ) ? space "}"
2025-10-28 03:37:52 -05:00
ref - definitions - foo - a - kv ::= " \" a \" " space ":" space number
2024-03-21 11:50:43 +00:00
root ::= alternative - 0 | alternative - 1
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
2025-10-28 03:37:52 -05:00
test ({
SUCCESS ,
"anyOf $ref" ,
R """({
"properties" : {
"a" : {
"anyOf" : [
{ "type" : "string" },
{ "type" : "number" }
]
},
"b" : {
"anyOf" : [
{ "$ref" : "#/properties/a/anyOf/0" },
{ "type" : "boolean" }
]
}
},
"type" : "object"
}) """,
R """(
a ::= string | number
a - kv ::= " \" a \" " space ":" space a
a - rest ::= ( "," space b - kv ) ?
b ::= b - 0 | boolean
b - 0 ::= string
b - kv ::= " \" b \" " space ":" space b
2026-06-20 17:43:04 -05:00
boolean ::= ( "true" | "false" )
2025-10-28 03:37:52 -05:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "{" space ( a - kv a - rest | b - kv ) ? space "}"
2025-10-28 03:37:52 -05:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2025-10-28 03:37:52 -05:00
) """
});
2024-03-21 11:50:43 +00:00
test ({
SUCCESS ,
"mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)" ,
R """({
"allOf" : [
{ "$ref" : "#/definitions/foo" },
{ "$ref" : "#/definitions/bar" },
{
"anyOf" : [
{ "$ref" : "#/definitions/baz" },
{ "$ref" : "#/definitions/bam" }
]
}
],
"definitions" : {
"foo" : {
2024-03-22 13:07:44 +00:00
"properties" : { "a" : { "type" : "number" }}
2024-03-21 11:50:43 +00:00
},
"bar" : {
2024-03-22 13:07:44 +00:00
"properties" : { "b" : { "type" : "number" }}
2024-03-21 11:50:43 +00:00
},
"bam" : {
2024-03-22 13:07:44 +00:00
"properties" : { "c" : { "type" : "number" }}
2024-03-21 11:50:43 +00:00
},
"baz" : {
2024-03-22 13:07:44 +00:00
"properties" : { "d" : { "type" : "number" }}
2024-03-21 11:50:43 +00:00
}
},
"type" : "object"
}) """,
R """(
a - kv ::= " \" a \" " space ":" space number
b - kv ::= " \" b \" " space ":" space number
c - kv ::= " \" c \" " space ":" space number
d - kv ::= " \" d \" " space ":" space number
2024-06-28 09:26:45 +01:00
d - rest ::= ( "," space c - kv ) ?
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
root ::= "{" space a - kv "," space b - kv ( "," space ( d - kv d - rest | c - kv ) ) ? space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
2025-09-08 16:14:32 -05:00
test ({
SUCCESS ,
"allOf with enum schema" ,
R """({
"allOf" : [
{ "$ref" : "#/definitions/foo" }
],
"definitions" : {
"foo" : {
"type" : "string" ,
"enum" : [ "a" , "b" ]
}
}
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( " \" a \" " | " \" b \" " )
2025-09-08 16:14:32 -05:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
) """
});
test ({
SUCCESS ,
"allOf with multiple enum schemas" ,
R """({
"allOf" : [
{ "$ref" : "#/definitions/foo" },
{ "$ref" : "#/definitions/bar" }
],
"definitions" : {
"foo" : {
"type" : "string" ,
"enum" : [ "a" , "b" , "c" ]
},
"bar" : {
"type" : "string" ,
"enum" : [ "b" , "c" , "d" ]
}
}
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= ( " \" b \" " | " \" c \" " )
2025-09-08 16:14:32 -05:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
) """
});
2024-03-21 11:50:43 +00:00
test ({
SUCCESS ,
"conflicting names" ,
R """({
"type" : "object" ,
"properties" : {
"number" : {
"type" : "object" ,
"properties" : {
"number" : {
"type" : "object" ,
2024-03-22 13:07:44 +00:00
"properties" : {
"root" : {
"type" : "number"
}
},
"required" : [
"root"
],
"additionalProperties" : false
2024-03-21 11:50:43 +00:00
}
},
"required" : [
"number"
],
"additionalProperties" : false
}
},
"required" : [
"number"
],
"additionalProperties" : false ,
"definitions" : {}
}) """,
R """(
2024-06-06 10:07:06 +01:00
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
number - ::= "{" space number - number - kv space "}"
2024-03-21 11:50:43 +00:00
number - kv ::= " \" number \" " space ":" space number -
2026-06-20 17:43:04 -05:00
number - number ::= "{" space number - number - root - kv space "}"
2024-03-21 11:50:43 +00:00
number - number - kv ::= " \" number \" " space ":" space number - number
number - number - root - kv ::= " \" root \" " space ":" space number
2026-06-20 17:43:04 -05:00
root ::= "{" space number - kv space "}"
2025-03-05 13:05:13 +00:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2024-03-21 11:50:43 +00:00
) """
});
2025-11-29 21:06:32 +05:00
2026-03-06 21:01:00 +01:00
test ({
SUCCESS ,
"description only (no type) treated as unconstrained" ,
R """({" description ": " The 0 - based index of the last line to be retrieved ( inclusive ). If None , read until the end of the file . "})""" ,
R """(
2026-06-20 17:43:04 -05:00
array ::= "[" space ( value ( "," space value ) * ) ? space "]"
boolean ::= ( "true" | "false" )
2026-03-06 21:01:00 +01:00
char ::= [ ^ " \\\x7F\x00 - \x1F ] | [ \\ ] ([" \\ bfnrt ] | "u" [ 0 - 9 a - fA - F ]{ 4 })
decimal - part ::= [ 0 - 9 ]{ 1 , 16 }
integral - part ::= [ 0 ] | [ 1 - 9 ] [ 0 - 9 ]{ 0 , 15 }
2026-06-20 17:43:04 -05:00
null ::= "null"
number ::= ( "-" ? integral - part ) ( "." decimal - part ) ? ([ eE ] [ -+ ] ? integral - part ) ?
object ::= "{" space ( string ":" space value ( "," space string ":" space value ) * ) ? space "}"
2026-03-06 21:01:00 +01:00
root ::= value
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
2026-06-20 17:43:04 -05:00
string ::= " \" " char * " \" "
2026-03-06 21:01:00 +01:00
value ::= object | array | string | number | boolean | null
) """
});
2025-11-29 21:06:32 +05:00
test ({
SUCCESS ,
"literal string with escapes" ,
R """({
"properties" : {
"code" : {
"const" : " \r \n \" \\ " ,
"description" : "Generated code" ,
"title" : "Code" ,
"type" : "string"
}
},
"required" : [
"code"
],
"title" : "DecoderResponse" ,
"type" : "object"
}) """,
R """(
2026-06-20 17:43:04 -05:00
code ::= " \" \\ r \\ n \\\" \\\\ \" "
2025-11-29 21:06:32 +05:00
code - kv ::= " \" code \" " space ":" space code
2026-06-20 17:43:04 -05:00
root ::= "{" space code - kv space "}"
2025-11-29 21:06:32 +05:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
) """
});
2024-03-21 11:50:43 +00:00
}
2025-12-16 04:05:23 -06:00
static void test_resolves_to_string () {
fprintf ( stderr , "# \n # Testing resolves_to_string \n # \n " );
auto test = []( const std :: string & name , const std :: string & schema_str , bool expected ) {
fprintf ( stderr , "- %s \n " , name . c_str ());
common_schema_info info ;
auto schema = nlohmann :: ordered_json :: parse ( schema_str );
info . resolve_refs ( schema );
bool result = info . resolves_to_string ( schema );
if ( result != expected ) {
fprintf ( stderr , "# \n # Test '%s' failed. \n # \n " , name . c_str ());
fprintf ( stderr , "Schema: %s \n " , schema_str . c_str ());
fprintf ( stderr , "Expected: %s, Got: %s \n " , expected ? "true" : "false" , result ? "true" : "false" );
assert ( false );
}
};
// Basic type checks
test ( "type string" , R "({" type ": " string "})" , true );
test ( "type integer" , R "({" type ": " integer "})" , false );
test ( "type number" , R "({" type ": " number "})" , false );
test ( "type boolean" , R "({" type ": " boolean "})" , false );
test ( "type object" , R "({" type ": " object "})" , false );
test ( "type array" , R "({" type ": " array "})" , false );
// Type array (nullable string)
test ( "type array with string" , R "({" type ": [" string ", " null "]})" , true );
test ( "type array without string" , R "({" type ": [" integer ", " null "]})" , false );
// String-specific keywords
test ( "minLength implies string" , R "({" minLength ": 1})" , true );
test ( "maxLength implies string" , R "({" maxLength ": 10})" , true );
test ( "pattern implies string" , R "({" pattern ": " ^ [ a - z ] + $ "})" , true );
// Format
test ( "format date" , R "({" format ": " date "})" , true );
test ( "format uuid" , R "({" format ": " uuid "})" , true );
test ( "format email" , R "({" format ": " email "})" , true );
// Const
test ( "const string" , R "({" const ": " hello "})" , true );
test ( "const number" , R "({" const ": 123})" , false );
// Enum
test ( "enum with strings" , R "({" enum ": [" a ", " b ", " c "]})" , true );
test ( "enum with numbers" , R "({" enum ": [1, 2, 3]})", false );
test ( "enum mixed with string" , R "({" enum ": [1, " a ", null]})" , true );
// anyOf
test ( "anyOf with string" , R "({" anyOf ": [{" type ": " string "}, {" type ": " integer "}]})" , true );
test ( "anyOf without string" , R "({" anyOf ": [{" type ": " integer "}, {" type ": " boolean "}]})" , false );
// oneOf
test ( "oneOf with string" , R "({" oneOf ": [{" type ": " string "}, {" type ": " number "}]})" , true );
test ( "oneOf without string" , R "({" oneOf ": [{" type ": " object "}, {" type ": " array "}]})" , false );
// allOf - all must be strings
test ( "allOf all strings" , R "({" allOf ": [{" type ": " string "}, {" minLength ": 1}]})" , true );
test ( "allOf mixed types" , R "({" allOf ": [{" type ": " string "}, {" type ": " integer "}]})" , false );
// $ref
test ( "$ref to string" ,
R "({" $ ref ": " # / $ defs / str ", " $ defs ": {" str ": {" type ": " string "}}})" , true );
test ( "$ref to integer" ,
R "({" $ ref ": " # / $ defs / num ", " $ defs ": {" num ": {" type ": " integer "}}})" , false );
// Nested
test ( "nested anyOf with string" ,
R "({" anyOf ": [{" anyOf ": [{" type ": " integer "}, {" type ": " string "}]}, {" type ": " boolean "}]})" , true );
fprintf ( stderr , "All resolves_to_string tests passed! \n " );
}
2024-03-21 11:50:43 +00:00
int main () {
2024-03-22 13:09:07 +00:00
fprintf ( stderr , "LLAMA_NODE_AVAILABLE = %s \n " , getenv ( "LLAMA_NODE_AVAILABLE" ) ? "true" : "false" );
fprintf ( stderr , "LLAMA_PYTHON_AVAILABLE = %s \n " , getenv ( "LLAMA_PYTHON_AVAILABLE" ) ? "true" : "false" );
2025-12-16 04:05:23 -06:00
test_resolves_to_string ();
2024-03-21 11:50:43 +00:00
test_all ( "C++" , []( const TestCase & tc ) {
try {
2025-02-01 23:55:32 -08:00
tc . verify ( json_schema_to_grammar ( nlohmann :: ordered_json :: parse ( tc . schema ), true ));
2024-03-21 11:50:43 +00:00
tc . verify_status ( SUCCESS );
2025-12-02 10:33:50 -06:00
} catch ( const std :: invalid_argument & ex ) {
2024-03-21 11:50:43 +00:00
fprintf ( stderr , "Error: %s \n " , ex . what ());
tc . verify_status ( FAILURE );
}
});
2024-03-22 13:09:07 +00:00
2026-03-28 17:55:38 +01:00
// C++ only tests (features not yet supported in JS/Python implementations)
{
fprintf ( stderr , "# \n # Testing C++ only features \n # \n " );
auto run = []( const TestCase & tc ) {
fprintf ( stderr , "- %s \n " , tc . name . c_str ());
try {
tc . verify ( json_schema_to_grammar ( nlohmann :: ordered_json :: parse ( tc . schema ), true ));
tc . verify_status ( SUCCESS );
} catch ( const std :: invalid_argument & ex ) {
fprintf ( stderr , "Error: %s \n " , ex . what ());
tc . verify_status ( FAILURE );
}
};
run ({
SUCCESS ,
"regexp with non-capturing group" ,
R """({
"type" : "string" ,
"pattern" : "^(?:foo|bar)baz$"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" " (( "foo" | "bar" ) "baz" ) " \" "
2026-03-28 17:55:38 +01:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
) """,
});
run ({
SUCCESS ,
"regexp with nested non-capturing groups" ,
R """({
"type" : "string" ,
"pattern" : "^(?:(?:ab)+c)?d$"
}) """,
R """(
2026-06-20 17:43:04 -05:00
root ::= " \" " ((( "ab" ) + "c" ) ? "d" ) " \" "
2026-03-28 17:55:38 +01:00
space ::= | " " | " \n " { 1 , 2 } [ \ t ]{ 0 , 20 }
) """,
});
}
2024-06-28 18:02:05 +01:00
if ( getenv ( "LLAMA_SKIP_TESTS_SLOW_ON_EMULATOR" )) {
fprintf ( stderr , " \033 [33mWARNING: Skipping slow tests on emulator. \n\033 [0m" );
2024-03-22 13:09:07 +00:00
} else {
2024-06-28 18:02:05 +01:00
if ( getenv ( "LLAMA_PYTHON_AVAILABLE" ) || ( std :: system ( "python -c \" import sys; exit(1) if sys.version_info < (3, 8) else print('Python version is sufficient') \" " ) == 0 )) {
test_all ( "Python" , []( const TestCase & tc ) {
write ( "test-json-schema-input.tmp" , tc . schema );
tc . verify_status ( std :: system (
"python ./examples/json_schema_to_grammar.py test-json-schema-input.tmp > test-grammar-output.tmp" ) == 0 ? SUCCESS : FAILURE );
tc . verify ( read ( "test-grammar-output.tmp" ));
});
} else {
fprintf ( stderr , " \033 [33mWARNING: Python not found (min version required is 3.8), skipping Python JSON schema -> grammar tests. \n\033 [0m" );
}
2024-03-22 13:09:07 +00:00
}
2024-03-21 11:50:43 +00:00
test_all ( "Check Expectations Validity" , []( const TestCase & tc ) {
if ( tc . expected_status == SUCCESS ) {
tc . verify_expectation_parseable ();
}
});
}