1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
#![recursion_limit = "512"]
//! Safe, idiomatic Rust bindings for the [xgrammar](https://github.com/mlc-ai/xgrammar)
//! C++ library for constrained decoding of large language models.
//!
//! This crate wraps xgrammar's grammar compilation and token-level matching so
//! you can drive constrained generation (JSON schema, regex, BNF, structural
//! tags) from Rust while retaining the performance of the upstream C++
//! implementation.
//!
//! # Highlights
//!
//! - [`Grammar`], [`GrammarCompiler`], [`CompiledGrammar`], [`TokenizerInfo`] —
//! compile grammars (BNF, JSON schema, regex, structural tags) against a
//! tokenizer.
//! - [`GrammarMatcher`] — token-by-token constrained decoding, including
//! [`GrammarMatcher::is_completed`] (root-rule match without stop token),
//! [`GrammarMatcher::fork`] for speculative / branching decoding, and
//! [`GrammarMatcher::traverse_draft_tree`] for filling per-node token
//! bitmasks over a speculative-decoding draft tree.
//! - [`BatchGrammarMatcher`] — batched helpers over a slice of matchers:
//! [`BatchGrammarMatcher::batch_fill_next_token_bitmask`] is parallel and
//! thread-pool-backed; `batch_accept_token` / `batch_accept_string` /
//! `batch_rollback` are sequential static helpers.
//! - Serialization — `serialize_json` / `deserialize_json` on [`Grammar`],
//! [`CompiledGrammar`], and [`TokenizerInfo`] for persisting compilation
//! results (version-locked to the vendored xgrammar's serialization format).
//! - Typed errors — [`XGrammarErr`] variants mirror the upstream xgrammar
//! exception types via `XGrammarError::GetType()`
//! ([`XGrammarErr::InvalidJson`], [`XGrammarErr::InvalidStructuralTag`],
//! [`XGrammarErr::DeserializeVersion`], [`XGrammarErr::DeserializeFormat`],
//! ...), with the coarse-grained variants kept as fallbacks for untyped C++
//! errors.
//!
//! See each item's documentation for usage details, including when a
//! `BatchGrammarMatcher` instance is required vs when associated functions can
//! be called directly.
mod error;
#[cfg(feature = "hf_hub")]
pub mod huggingface_hub;
use std::collections::HashMap;
use std::ffi::CStr;
use std::path::Path;
use std::str::FromStr;
use cpp::{cpp, cpp_class};
use dlpark::{traits::TensorView, versioned::SafeManagedTensorVersioned as DLTensor};
pub use error::XGrammarErr;
use serde_json::Value;
pub use tokenizers;
/// Alias for `std::result::Result<T, XGrammarErr>`.
pub type Result<T> = std::result::Result<T, XGrammarErr>;
pub type VocabMap = std::collections::HashMap<String, u32>;
pub type TokenId = i32;
cpp! {{
#include "xgrammar/xgrammar.h"
#include <picojson.h>
#include <cstring>
using namespace std;
using namespace xgrammar;
using namespace picojson;
struct MetadataFromHF {
VocabType vocab_type;
bool add_prefix_space;
};
// Discriminants for the xgrammar::XGrammarError subclasses. Keep the
// numeric values in sync with the match in the Rust `error_from_kind`
// function (defined alongside the result-struct mirrors).
enum XGErrorKind : int32_t {
kXGOtherError = 0,
kXGDeserializeVersionError = 1,
kXGInvalidJSONError = 2,
kXGDeserializeFormatError = 3,
kXGInvalidJSONSchemaError = 4,
kXGInvalidStructuralTagError = 5,
};
// Classify any exception into an XGErrorKind. dynamic_cast (rather than
// comparing XGrammarError::GetType() strings) so that an upstream class
// rename breaks compilation loudly instead of silently degrading every
// typed error to kXGOtherError. Taking std::exception lets a single
// catch clause handle both typed and untyped errors.
static int32_t xgr_error_kind(const std::exception& e) {
if (dynamic_cast<const xgrammar::DeserializeVersionError*>(&e)) {
return kXGDeserializeVersionError;
}
if (dynamic_cast<const xgrammar::InvalidJSONError*>(&e)) {
return kXGInvalidJSONError;
}
if (dynamic_cast<const xgrammar::DeserializeFormatError*>(&e)) {
return kXGDeserializeFormatError;
}
if (dynamic_cast<const xgrammar::InvalidJSONSchemaError*>(&e)) {
return kXGInvalidJSONSchemaError;
}
if (dynamic_cast<const xgrammar::InvalidStructuralTagError*>(&e)) {
return kXGInvalidStructuralTagError;
}
return kXGOtherError;
}
// Extract the message and typed error kind from a std::variant of
// XGrammarError subclasses (xgrammar::SerializationError or
// xgrammar::StructuralTagError). The visitor can take the base reference
// because every variant alternative derives from XGrammarError.
template <typename V>
static char* xgr_dup_variant_error(const V& error, int32_t* error_kind) {
std::string error_msg;
std::visit([&](const xgrammar::XGrammarError& err) {
error_msg = err.what();
*error_kind = xgr_error_kind(err);
}, error);
return strdup(error_msg.c_str());
}
// Keep the layout of every *Result struct in sync with its #[repr(C)]
// mirror on the Rust side. `error_kind` (an XGErrorKind value) is
// deliberately the LAST field: C++ aggregate initialization zero-fills
// omitted trailing members, so an init site that forgets it degrades to
// kind 0 (context fallback) instead of undefined behavior.
struct GrammarResult {
bool success;
Grammar grammar;
char* error_message;
int32_t error_kind;
};
struct CompiledGrammarResult {
bool success;
CompiledGrammar compiled_grammar;
char* error_message;
int32_t error_kind;
};
struct MatcherResult {
bool success;
bool value;
char* error_message;
int32_t error_kind;
};
struct TokenizerInfoResult {
bool success;
TokenizerInfo tokenizer_info;
char* error_message;
int32_t error_kind;
};
}}
cpp_class!(
pub unsafe struct TokenizerInfo as "xgrammar::TokenizerInfo"
);
cpp_class!(
pub unsafe struct GrammarCompiler as "xgrammar::GrammarCompiler"
);
cpp_class!(
pub unsafe struct CompiledGrammar as "xgrammar::CompiledGrammar"
);
cpp_class!(
pub unsafe struct Grammar as "xgrammar::Grammar"
);
cpp_class!(
pub unsafe struct GrammarMatcher as "xgrammar::GrammarMatcher"
);
cpp_class!(
pub unsafe struct BatchGrammarMatcher as "xgrammar::BatchGrammarMatcher"
);
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VocabType {
Raw = 0,
ByteFallback = 1,
ByteLevel = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MetadataFromHF {
pub vocab_type: VocabType,
pub add_prefix_space: bool,
}
/// Helper function to safely extract and free C++ error message.
///
/// # Safety
/// The error_message_ptr must be null or a valid C string pointer allocated
/// with strdup.
unsafe fn extract_and_free_error_message(error_message_ptr: *mut std::os::raw::c_char) -> String {
// Every failure path fills the pointer with strdup(), which returns NULL
// when allocation fails — degrade to a placeholder instead of passing
// NULL to CStr::from_ptr.
if error_message_ptr.is_null() {
return "<error message unavailable: allocation failure>".to_string();
}
// SAFETY: The caller guarantees that error_message_ptr is a valid C string
// allocated with strdup; the null case is handled above.
unsafe {
let msg = CStr::from_ptr(error_message_ptr).to_string_lossy().into_owned();
libc::free(error_message_ptr as *mut libc::c_void);
msg
}
}
/// Copy a C++-owned byte buffer into `*out` as a Rust `String`. Shared body of
/// the `rust!` callbacks in the `serialize_json` bindings.
///
/// The buffer is marshaled as `*const u8` (not `c_char`) so no platform-varying
/// cast is needed (`c_char` is `i8` on x86_64 but `u8` on arm64). The upstream
/// serializer maps raw bytes to Latin-1 code points (`ByteToLatin1`), so the
/// JSON text is valid UTF-8 by construction; the lossy conversion is defensive
/// only.
///
/// # Safety
/// `data`/`len` must describe a readable buffer that outlives the call, and
/// `out` must point to a live `String`.
unsafe fn assign_utf8_lossy(out: *mut String, data: *const u8, len: usize) {
let slice = unsafe { std::slice::from_raw_parts(data, len) };
unsafe { *out = String::from_utf8_lossy(slice).into_owned() };
}
/// Ensure a DLPack tensor is row-major contiguous before handing it to
/// xgrammar, which indexes `data` as a compact row-major array and ignores
/// `DLTensor::strides` entirely. A permuted or sliced layout would be read
/// and written at wrong offsets (silently corrupting results), and a
/// negative stride would place accesses outside the buffer.
fn ensure_row_major_contiguous(tensor: &DLTensor, name: &str) -> Result<()> {
if tensor.memory_order() == dlpark::utils::MemoryOrder::RowMajorContiguous {
return Ok(());
}
Err(XGrammarErr::MatcherError(format!(
"The {} tensor must be row-major contiguous: xgrammar ignores DLTensor strides \
(shape {:?}, strides {:?})",
name,
tensor.shape(),
tensor.strides(),
)))
}
/// View a DLPack tensor as an `i64` slice when it is a contiguous 1-D int64
/// CPU tensor; `None` otherwise (the C++ side then reports its own
/// validation error for the malformed tensor). The device check is
/// load-bearing: a non-CPU data pointer must not be dereferenced on the
/// host.
fn as_cpu_i64_slice(tensor: &DLTensor) -> Option<&[i64]> {
let dtype = tensor.data_type();
if tensor.dl_tensor().device.device_type != dlpark::ffi::DeviceType::Cpu
|| tensor.num_dimensions() != 1
|| dtype.code != dlpark::ffi::DataTypeCode::Int
|| dtype.bits != 64
|| dtype.lanes != 1
{
return None;
}
tensor.as_slice_contiguous::<i64>().ok()
}
/// Validate a draft-tree encoding before handing the tensors to xgrammar:
/// upstream `TraverseDraftTree` follows `retrieve_next_token` /
/// `retrieve_next_sibling` indices without any bounds or cycle check, so an
/// out-of-range link would corrupt memory and a cycle would recurse forever.
/// Every link must stay in `[-1, N)` and each node may be reached at most
/// once (the encoding must be a tree).
fn validate_draft_tree(next_token: &[i64], next_sibling: &[i64]) -> Result<()> {
let n = next_token.len();
if n == 0 {
return Ok(()); // The C++ side rejects empty tensors itself.
}
let mut visited = vec![false; n];
let mut stack = vec![0usize];
while let Some(node) = stack.pop() {
if visited[node] {
return Err(XGrammarErr::MatcherError(format!(
"Invalid draft tree: node {node} is reachable more than once \
(the encoding must be an acyclic tree)"
)));
}
visited[node] = true;
for link in [next_token[node], next_sibling[node]] {
if link == -1 {
continue;
}
if link < 0 || link >= n as i64 {
return Err(XGrammarErr::MatcherError(format!(
"Invalid draft tree: node {node} links to index {link}, outside [-1, {n})"
)));
}
stack.push(link as usize);
}
}
Ok(())
}
/// Map a C++ `XGErrorKind` discriminant + message to a precise
/// [`XGrammarErr`], falling back to the per-result-type context variant when
/// the exception was untyped (kind 0, e.g. a plain `std::runtime_error` from
/// `XGRAMMAR_CHECK`). Keep the discriminants in sync with `XGErrorKind` in the
/// `cpp!{{...}}` block above; an out-of-range value degrades to the fallback.
fn error_from_kind(raw_kind: i32, msg: String, fallback: fn(String) -> XGrammarErr) -> XGrammarErr {
match raw_kind {
1 => XGrammarErr::DeserializeVersion(msg),
2 => XGrammarErr::InvalidJson(msg),
3 => XGrammarErr::DeserializeFormat(msg),
4 => XGrammarErr::InvalidJsonSchema(msg),
5 => XGrammarErr::InvalidStructuralTag(msg),
_ => fallback(msg),
}
}
// Keep in sync with the `GrammarResult` C++ struct in the cpp!{{...}} block.
#[repr(C)]
pub(crate) struct GrammarResult {
pub success: bool,
pub grammar: Grammar,
pub error_message: *mut std::os::raw::c_char,
pub error_kind: i32,
}
impl Drop for GrammarResult {
fn drop(&mut self) {
if !self.error_message.is_null() {
unsafe {
libc::free(self.error_message as *mut libc::c_void);
}
}
}
}
impl From<GrammarResult> for Result<Grammar> {
fn from(result: GrammarResult) -> Self {
use std::mem::ManuallyDrop;
// Wrap in ManuallyDrop to prevent automatic drop
let result = ManuallyDrop::new(result);
if result.success {
// SAFETY: We're taking ownership and preventing double-free by using ManuallyDrop
unsafe { Ok(std::ptr::read(&result.grammar)) }
} else {
// SAFETY: error_message is valid and we're taking ownership
let error_msg = unsafe { extract_and_free_error_message(result.error_message) };
Err(error_from_kind(result.error_kind, error_msg, XGrammarErr::InvalidGrammar))
}
}
}
// Keep in sync with the `CompiledGrammarResult` C++ struct in the cpp!{{...}} block.
#[repr(C)]
pub(crate) struct CompiledGrammarResult {
pub success: bool,
pub compiled_grammar: CompiledGrammar,
pub error_message: *mut std::os::raw::c_char,
pub error_kind: i32,
}
impl Drop for CompiledGrammarResult {
fn drop(&mut self) {
if !self.error_message.is_null() {
unsafe {
libc::free(self.error_message as *mut libc::c_void);
}
}
}
}
impl From<CompiledGrammarResult> for Result<CompiledGrammar> {
fn from(result: CompiledGrammarResult) -> Self {
use std::mem::ManuallyDrop;
// Wrap in ManuallyDrop to prevent automatic drop
let result = ManuallyDrop::new(result);
if result.success {
// SAFETY: We're taking ownership and preventing double-free by using ManuallyDrop
unsafe { Ok(std::ptr::read(&result.compiled_grammar)) }
} else {
// SAFETY: error_message is valid and we're taking ownership
let error_msg = unsafe { extract_and_free_error_message(result.error_message) };
Err(error_from_kind(result.error_kind, error_msg, XGrammarErr::CompilationError))
}
}
}
// Keep in sync with the `MatcherResult` C++ struct in the cpp!{{...}} block.
#[repr(C)]
pub(crate) struct MatcherResult {
pub success: bool,
pub value: bool,
pub error_message: *mut std::os::raw::c_char,
pub error_kind: i32,
}
impl Drop for MatcherResult {
fn drop(&mut self) {
if !self.error_message.is_null() {
unsafe {
libc::free(self.error_message as *mut libc::c_void);
}
}
}
}
impl From<MatcherResult> for Result<bool> {
fn from(result: MatcherResult) -> Self {
use std::mem::ManuallyDrop;
// Wrap in ManuallyDrop to prevent automatic drop
let result = ManuallyDrop::new(result);
if result.success {
Ok(result.value)
} else {
// SAFETY: error_message is valid and we're taking ownership
let error_msg = unsafe { extract_and_free_error_message(result.error_message) };
Err(error_from_kind(result.error_kind, error_msg, XGrammarErr::MatcherError))
}
}
}
impl From<MatcherResult> for Result<()> {
fn from(result: MatcherResult) -> Self {
use std::mem::ManuallyDrop;
// Wrap in ManuallyDrop to prevent automatic drop
let result = ManuallyDrop::new(result);
if result.success {
Ok(())
} else {
// SAFETY: error_message is valid and we're taking ownership
let error_msg = unsafe { extract_and_free_error_message(result.error_message) };
Err(error_from_kind(result.error_kind, error_msg, XGrammarErr::MatcherError))
}
}
}
// Keep in sync with the `TokenizerInfoResult` C++ struct in the cpp!{{...}} block.
#[repr(C)]
pub(crate) struct TokenizerInfoResult {
pub success: bool,
pub tokenizer_info: TokenizerInfo,
pub error_message: *mut std::os::raw::c_char,
pub error_kind: i32,
}
impl Drop for TokenizerInfoResult {
fn drop(&mut self) {
if !self.error_message.is_null() {
unsafe {
libc::free(self.error_message as *mut libc::c_void);
}
}
}
}
impl From<TokenizerInfoResult> for Result<TokenizerInfo> {
fn from(result: TokenizerInfoResult) -> Self {
use std::mem::ManuallyDrop;
// Wrap in ManuallyDrop to prevent automatic drop
let result = ManuallyDrop::new(result);
if result.success {
// SAFETY: We're taking ownership and preventing double-free by using ManuallyDrop
unsafe { Ok(std::ptr::read(&result.tokenizer_info)) }
} else {
// SAFETY: error_message is valid and we're taking ownership
let error_msg = unsafe { extract_and_free_error_message(result.error_message) };
Err(error_from_kind(result.error_kind, error_msg, XGrammarErr::TokenizerInfoError))
}
}
}
pub static HF_CONFIG_FILE: &str = "config.json";
pub static TOKENIZER_FILE: &str = "tokenizer.json";
pub static TOKENIZER_CONFIG_FILE: &str = "tokenizer_config.json";
pub static GENERATION_CONFIG_FILE: &str = "generation_config.json";
pub static TOKENIZER_ALLOW_PATTERN: &[&str] =
&[TOKENIZER_FILE, TOKENIZER_CONFIG_FILE, GENERATION_CONFIG_FILE];
pub static TOKENIZER_MODEL_KEY: &str = "model";
pub static TOKENIZER_VOCAB_KEY: &str = "vocab";
pub static EOS_TOKEN_ID_KEY: &str = "eos_token_id";
impl TokenizerInfo {
pub fn from_backend_str(
backend_str: &str,
vocab_size: Option<usize>,
stop_token_ids: Vec<TokenId>,
) -> self::Result<Self> {
let tokenizer = tokenizers::Tokenizer::from_str(backend_str).map_err(|e| {
XGrammarErr::TokenizerParseFailed(format!("failed to parse tokenizer: {}", e))
})?;
let vocab_map = tokenizer.get_vocab(true); // with added special tokens
let max_id = vocab_map
.values()
.max()
.ok_or(XGrammarErr::InvalidTokenizerConfig("Vocab map is empty".to_string()))?;
let tokenizer_vocab_size = std::cmp::max(vocab_map.len(), (max_id + 1) as usize);
if let Some(vocab_size) = vocab_size
&& vocab_size != tokenizer_vocab_size
{
tracing::warn!(
"Provided vocab_size {} does not match tokenizer vocab size {}. Using provided vocab_size.",
vocab_size,
tokenizer_vocab_size
);
}
let final_vocab_size = vocab_size.unwrap_or(tokenizer_vocab_size);
let tokenizer_metadata = Self::detect_metadata_from_hf(backend_str)?;
let vocab_type = tokenizer_metadata.vocab_type;
let add_prefix_space = tokenizer_metadata.add_prefix_space;
Self::new(vocab_map, vocab_type, final_vocab_size, stop_token_ids, add_prefix_space)
}
pub fn parse_eos_token(path: &Path, json_key: &str) -> Option<Vec<i32>> {
let contents = std::fs::read_to_string(path).ok()?;
let json: Value = serde_json::from_str(&contents).ok()?;
match json.get(json_key) {
Some(Value::Number(num)) if num.is_i64() => Some(vec![num.as_i64().unwrap() as i32]),
Some(Value::Array(arr)) => {
let mut eos_tokens = Vec::new();
for item in arr {
if let Value::Number(num) = item {
if num.is_i64() {
eos_tokens.push(num.as_i64().unwrap() as i32);
} else {
return None;
}
} else {
return None;
}
}
Some(eos_tokens)
}
_ => None,
}
}
pub fn from_path<P>(
path: P,
vocab_size: Option<usize>,
stop_token_ids: Option<Vec<TokenId>>,
) -> Result<Self>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let tokenizer_json_path = path.join(TOKENIZER_FILE);
let backend_str = std::fs::read_to_string(&tokenizer_json_path)
.map_err(XGrammarErr::TokenizerLoadFailed)?;
let eos_token = Self::parse_eos_token(&path.join(GENERATION_CONFIG_FILE), EOS_TOKEN_ID_KEY)
.or_else(|| Self::parse_eos_token(&path.join(HF_CONFIG_FILE), EOS_TOKEN_ID_KEY));
let mut stop_token_ids = stop_token_ids.unwrap_or_default();
stop_token_ids.extend(eos_token.unwrap_or_default());
stop_token_ids.dedup();
Self::from_backend_str(&backend_str, vocab_size, stop_token_ids)
}
#[cfg(feature = "hf_hub")]
pub fn from_pretrained(
tokenizer_id: &str,
revision: Option<String>,
vocab_size: Option<usize>,
stop_token_ids: Option<Vec<i32>>,
) -> Result<TokenizerInfo> {
use huggingface_hub::{Params, Repo, RepoType, compile_glob_pattern, snapshot_download};
let allow_patterns = compile_glob_pattern(TOKENIZER_ALLOW_PATTERN).map_err(|e| {
XGrammarErr::TokenizerParseFailed(format!("Failed to compile glob patterns: {}", e))
})?;
let download_options =
Some(Params { allow_patterns: Some(allow_patterns), ..Default::default() });
let repo = Repo::with_revision(
tokenizer_id.to_string(),
RepoType::Model,
revision.unwrap_or("main".to_string()),
);
let tokenizer_dir = snapshot_download(repo, download_options)?;
Self::from_path(tokenizer_dir, vocab_size, stop_token_ids)
}
fn new(
vocab_map: HashMap<String, u32>,
vocab_type: VocabType,
vocab_size: usize,
stop_token_ids: Vec<i32>,
add_prefix_space: bool,
) -> self::Result<Self> {
// Ensure the vocab size is at least as large as the max id in the vocab map.
// Marshal each token as pointer + length (not CString): tokens are
// arbitrary byte sequences and may legally contain NUL bytes, which
// upstream's std::vector<std::string> preserves.
let mut encoded_vocab: Vec<&str> = vec![""; vocab_size];
// Fill the encoded_vocab with tokens from the vocab_map
for (token, idx) in vocab_map.iter() {
assert!(
(*idx as usize) < vocab_size,
"Token ID {} exceeds vocab size {}",
idx,
vocab_size
);
encoded_vocab[*idx as usize] = token.as_str();
}
let token_ptrs: Vec<*const u8> = encoded_vocab.iter().map(|s| s.as_ptr()).collect();
let token_lens: Vec<usize> = encoded_vocab.iter().map(|s| s.len()).collect();
let token_ptrs_ptr = token_ptrs.as_ptr();
let token_lens_ptr = token_lens.as_ptr();
let vocab_size_i32 = vocab_size as i32;
let stop_token_ids_ptr = stop_token_ids.as_ptr();
let stop_token_ids_len = stop_token_ids.len();
Ok(cpp!(unsafe [
token_ptrs_ptr as "const uint8_t* const*",
token_lens_ptr as "const size_t*",
vocab_type as "xgrammar::VocabType",
vocab_size_i32 as "int",
stop_token_ids_ptr as "const int32_t*",
stop_token_ids_len as "size_t",
add_prefix_space as "bool"
] -> TokenizerInfo as "xgrammar::TokenizerInfo" {
std::vector<std::string> encoded_vocab;
encoded_vocab.reserve(vocab_size_i32);
for (int i = 0; i < vocab_size_i32; ++i) {
encoded_vocab.emplace_back(
reinterpret_cast<const char*>(token_ptrs_ptr[i]), token_lens_ptr[i]
);
}
std::vector<int32_t> stop_token_ids(stop_token_ids_ptr, stop_token_ids_ptr + stop_token_ids_len);
return xgrammar::TokenizerInfo(
encoded_vocab,
vocab_type,
vocab_size_i32,
stop_token_ids,
add_prefix_space
);
}))
}
// // VocabType GetVocabType() const;
pub fn get_vocab_type(&self) -> VocabType {
cpp!(unsafe [self as "const xgrammar::TokenizerInfo*"] -> VocabType as "xgrammar::VocabType" {
return self->GetVocabType();
})
}
// bool GetAddPrefixSpace() const;
pub fn get_add_prefix_space(&self) -> bool {
cpp!(unsafe [self as "const xgrammar::TokenizerInfo*"] -> bool as "bool" {
return self->GetAddPrefixSpace();
})
}
// int GetVocabSize() const;
pub fn get_vocab_size(&self) -> i32 {
cpp!(unsafe [self as "const xgrammar::TokenizerInfo*"] -> i32 as "int" {
return self->GetVocabSize();
})
}
// const std::vector<std::string>& GetDecodedVocab() const;
pub fn get_decoded_vocab(&self) -> Vec<String> {
// Avoid relying on layout-compatibility between `Vec<T>` and `std::vector<T>`
// (Rust Vec is (ptr, cap, len); libstdc++ std::vector is (start, finish,
// end_of_storage) — different semantics for the second/third word). Instead
// the C++ side writes each element into a Rust-allocated `Vec<String>` via
// the `vec_push_string` callback bridge.
let mut out: Vec<String> = Vec::new();
let out_ptr = &mut out as *mut Vec<String>;
cpp!(unsafe [
self as "const xgrammar::TokenizerInfo*",
out_ptr as "void*"
] {
const auto& vocab = self->GetDecodedVocab();
for (const auto& s : vocab) {
// Marshal as `uint8_t*` so the Rust side receives `*const u8`
// directly, avoiding a `c_char`→`u8` cast whose necessity
// varies by platform (c_char is i8 on x86_64 but u8 on arm64,
// which makes the cast trigger `clippy::unnecessary_cast`
// on arm64).
const uint8_t* data = reinterpret_cast<const uint8_t*>(s.data());
size_t len = s.size();
rust!(XGR_TokInfo_DecodedVocab_push [
out_ptr: *mut Vec<String> as "void*",
data: *const u8 as "const uint8_t*",
len: usize as "size_t"
] {
// SAFETY: `data`/`len` point into the C++ std::string; the
// slice is only read during this call. `out_ptr` was
// obtained from a live `&mut Vec<String>` on the Rust side.
let slice = unsafe { std::slice::from_raw_parts(data, len) };
// Must be `from_utf8_lossy`, not `from_utf8_unchecked`:
// xgrammar's ByteFallback / ByteLevel decoders (see
// thirdparty/xgrammar/cpp/tokenizer_info.cc `DecodeToken`)
// can return single raw bytes (e.g. `<0x80>` → 0x80), which
// are not valid UTF-8. `unchecked` would be UB here.
let s = String::from_utf8_lossy(slice).into_owned();
unsafe { (*out_ptr).push(s) };
});
}
});
out
}
/// Serialize the tokenizer info to a JSON string.
///
/// The output is version-locked like [`Grammar::serialize_json`].
///
/// Non-UTF-8 vocab bytes survive the round-trip (the upstream serializer
/// maps raw bytes to Latin-1 code points, so the JSON text is always valid
/// UTF-8), with one upstream limitation: vocab tokens containing a NUL
/// byte are truncated at the first NUL during serialization (xgrammar
/// v0.2.3, `ByteToLatin1` in `cpp/support/encoding.h`).
pub fn serialize_json(&self) -> String {
let mut out = String::new();
let out_ptr = &mut out as *mut String;
cpp!(unsafe [self as "const xgrammar::TokenizerInfo*", out_ptr as "void*"] {
std::string json = self->SerializeJSON();
const uint8_t* data = reinterpret_cast<const uint8_t*>(json.data());
size_t len = json.size();
rust!(XGR_TokInfo_Serialize_set [
out_ptr: *mut String as "void*",
data: *const u8 as "const uint8_t*",
len: usize as "size_t"
] {
// SAFETY: `data`/`len` point into the C++ std::string, which
// outlives this call; `out_ptr` comes from a live `&mut String`.
unsafe { crate::assign_utf8_lossy(out_ptr, data, len) };
});
});
out
}
/// Deserialize a tokenizer info from a JSON string produced by
/// [`Self::serialize_json`].
///
/// # Errors
/// * [`XGrammarErr::DeserializeVersion`] if the data was serialized by an
/// incompatible xgrammar serialization version
/// * [`XGrammarErr::InvalidJson`] if the input is not valid JSON
/// * [`XGrammarErr::DeserializeFormat`] if the JSON does not have the
/// expected structure
/// * [`XGrammarErr::TokenizerInfoError`] if an untyped xgrammar error is
/// raised while reconstructing the tokenizer info (e.g. valid JSON that
/// is not an object)
pub fn deserialize_json(json: &str) -> Result<Self> {
// Pointer + length marshaling — see Grammar::deserialize_json for the
// rationale.
let json_ptr = json.as_ptr();
let json_len = json.len();
let result = cpp!(unsafe [
json_ptr as "const uint8_t*",
json_len as "size_t"
] -> TokenizerInfoResult as "TokenizerInfoResult" {
try {
std::string json_str(reinterpret_cast<const char*>(json_ptr), json_len);
auto result = xgrammar::TokenizerInfo::DeserializeJSON(json_str);
if (std::holds_alternative<xgrammar::TokenizerInfo>(result)) {
return {true, std::get<xgrammar::TokenizerInfo>(result), nullptr, 0};
} else {
int32_t error_kind = kXGOtherError;
char* error_message = xgr_dup_variant_error(
std::get<xgrammar::SerializationError>(result), &error_kind
);
return {false, TokenizerInfo(NullObj()), error_message, error_kind};
}
} catch (const std::exception& e) {
return {false, TokenizerInfo(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
fn detect_metadata_from_hf(backend_str: &str) -> Result<MetadataFromHF> {
let backend_str_ptr = backend_str.as_ptr();
let backend_str_len = backend_str.len();
// Error out-parameter: set to a strdup'd message when the C++ side
// fails. C++ exceptions must not cross the FFI boundary, so the
// closure catches everything and reports through this channel.
let mut error_message: *mut std::os::raw::c_char = std::ptr::null_mut();
let error_message_out = &mut error_message as *mut *mut std::os::raw::c_char;
let metadata = cpp!(unsafe [
backend_str_ptr as "const uint8_t*",
backend_str_len as "size_t",
error_message_out as "char**"
] -> MetadataFromHF as "MetadataFromHF" {
try {
std::string backend_str(
reinterpret_cast<const char*>(backend_str_ptr), backend_str_len
);
std::string metadata_str = TokenizerInfo::DetectMetadataFromHF(backend_str);
picojson::value v;
std::string err = picojson::parse(v, metadata_str);
if (!err.empty()) {
throw std::runtime_error("Failed to parse metadata: " + err);
}
const picojson::object& metadata = v.get<picojson::object>();
MetadataFromHF metadata_from_hf;
metadata_from_hf.vocab_type = static_cast<xgrammar::VocabType>(metadata["vocab_type"].get<double>());
metadata_from_hf.add_prefix_space = metadata["add_prefix_space"].get<bool>();
return metadata_from_hf;
} catch (const std::exception& e) {
*error_message_out = strdup(e.what());
return MetadataFromHF{VocabType::RAW, false};
}
});
if !error_message.is_null() {
// SAFETY: non-null means the C++ side just strdup'd it.
let msg = unsafe { extract_and_free_error_message(error_message) };
return Err(XGrammarErr::TokenizerParseFailed(msg));
}
Ok(metadata)
}
}
impl CompiledGrammar {
pub fn get_grammar(&self) -> Grammar {
cpp!(unsafe [self as "const xgrammar::CompiledGrammar*"] -> Grammar as "xgrammar::Grammar" {
return self->GetGrammar();
})
}
/// Return the tokenizer info associated with this compiled grammar.
pub fn get_tokenizer_info(&self) -> TokenizerInfo {
cpp!(unsafe [self as "const xgrammar::CompiledGrammar*"] -> TokenizerInfo as "xgrammar::TokenizerInfo" {
return self->GetTokenizerInfo();
})
}
/// Return the approximate memory usage of the grammar in bytes.
pub fn memory_size_bytes(&self) -> usize {
cpp!(unsafe [self as "const xgrammar::CompiledGrammar*"] -> usize as "size_t" {
return self->MemorySizeBytes();
})
}
/// Serialize the compiled grammar to a JSON string.
///
/// The output contains the grammar, the precomputed token masks, and a
/// tokenizer-metadata fingerprint (vocab type/size, `add_prefix_space`,
/// stop token ids) — but not the full tokenizer info, so the same
/// tokenizer info must be supplied again to [`Self::deserialize_json`],
/// which validates it against the fingerprint. It is version-locked like
/// [`Grammar::serialize_json`].
pub fn serialize_json(&self) -> String {
let mut out = String::new();
let out_ptr = &mut out as *mut String;
cpp!(unsafe [self as "const xgrammar::CompiledGrammar*", out_ptr as "void*"] {
std::string json = self->SerializeJSON();
const uint8_t* data = reinterpret_cast<const uint8_t*>(json.data());
size_t len = json.size();
rust!(XGR_CompiledGrammar_Serialize_set [
out_ptr: *mut String as "void*",
data: *const u8 as "const uint8_t*",
len: usize as "size_t"
] {
// SAFETY: `data`/`len` point into the C++ std::string, which
// outlives this call; `out_ptr` comes from a live `&mut String`.
unsafe { crate::assign_utf8_lossy(out_ptr, data, len) };
});
});
out
}
/// Deserialize a compiled grammar from a JSON string produced by
/// [`Self::serialize_json`], re-attaching the given tokenizer info.
///
/// # Arguments
/// * `json` - The serialized compiled grammar
/// * `tokenizer_info` - The tokenizer info the grammar was compiled
/// against. The serialized data embeds a metadata fingerprint of the
/// original tokenizer, and deserialization fails if the supplied
/// tokenizer info does not match it.
///
/// # Errors
/// * [`XGrammarErr::DeserializeVersion`] if the data was serialized by an
/// incompatible xgrammar serialization version
/// * [`XGrammarErr::InvalidJson`] if the input is not valid JSON
/// * [`XGrammarErr::DeserializeFormat`] if the JSON does not have the
/// expected structure, or if `tokenizer_info` does not match the
/// embedded tokenizer metadata (`"Tokenizer metadata mismatch: ..."`)
/// * [`XGrammarErr::CompilationError`] if an untyped xgrammar error is
/// raised while reconstructing the compiled grammar
///
/// Note: the vendored xgrammar ignores failures while deserializing the
/// embedded `adaptive_token_mask_cache` payload, so a corrupted mask
/// cache may still deserialize without an error.
pub fn deserialize_json(json: &str, tokenizer_info: &TokenizerInfo) -> Result<Self> {
// Pointer + length marshaling — see Grammar::deserialize_json for the
// rationale.
let json_ptr = json.as_ptr();
let json_len = json.len();
let result = cpp!(unsafe [
json_ptr as "const uint8_t*",
json_len as "size_t",
tokenizer_info as "const xgrammar::TokenizerInfo*"
] -> CompiledGrammarResult as "CompiledGrammarResult" {
try {
std::string json_str(reinterpret_cast<const char*>(json_ptr), json_len);
auto result = xgrammar::CompiledGrammar::DeserializeJSON(json_str, *tokenizer_info);
if (std::holds_alternative<xgrammar::CompiledGrammar>(result)) {
return {true, std::get<xgrammar::CompiledGrammar>(result), nullptr, 0};
} else {
int32_t error_kind = kXGOtherError;
char* error_message = xgr_dup_variant_error(
std::get<xgrammar::SerializationError>(result), &error_kind
);
return {false, CompiledGrammar(NullObj()), error_message, error_kind};
}
} catch (const std::exception& e) {
return {false, CompiledGrammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
let compiled = Result::<Self>::from(result)?;
// The vendored xgrammar ignores the error returned while
// deserializing the "grammar" field (see DeserializeJSONValue in
// thirdparty/xgrammar/cpp/compiled_grammar.cc), which leaves the
// field as a null grammar and would otherwise surface much later as
// a crash inside the matcher. Detect that tell-tale state here.
if compiled.get_grammar().is_null() {
return Err(XGrammarErr::DeserializeFormat(
"Deserialize error for type CompiledGrammar: the 'grammar' field failed to \
deserialize"
.to_string(),
));
}
Ok(compiled)
}
}
impl GrammarCompiler {
/// Create a new GrammarCompiler with default parameters.
///
/// The GrammarCompiler is a grammar compilation utility that compiles various types of
/// grammars into CompiledGrammar objects. It is associated with a specific tokenizer
/// and supports caching of grammar compilation results.
///
/// # Arguments
/// * `tokenizer_info` - The tokenizer info to use for the grammar compiler
///
/// # Returns
/// * A new GrammarCompiler instance with default settings (max_threads: 1, cache enabled)
pub fn new(tokenizer_info: &TokenizerInfo) -> Self {
Self::with(tokenizer_info, None, None, None)
}
/// Create a new GrammarCompiler with custom parameters.
///
/// This allows fine-grained control over compilation behavior including thread usage,
/// caching, and memory limits.
///
/// # Arguments
/// * `tokenizer_info` - The tokenizer info to use for the grammar compiler
/// * `max_threads` - The maximum number of threads to use for parallel compilation (default: 1)
/// * `cache_enabled` - Whether to enable caching of compiled grammars (default: true)
/// * `max_memory_bytes` - The maximum memory in bytes to use for caching. Use None for unlimited.
///
/// # Returns
/// * A new GrammarCompiler instance with the specified settings
pub fn with(
tokenizer_info: &TokenizerInfo,
max_threads: Option<usize>,
cache_enabled: Option<bool>,
max_memory_bytes: Option<usize>,
) -> Self {
let max_threads = max_threads.unwrap_or(1) as i32;
let cache_enabled = cache_enabled.unwrap_or(true);
let max_memory_bytes: i64 = max_memory_bytes.map(|v| v as i64).unwrap_or(-1);
let grammar_compiler = cpp!(unsafe [
tokenizer_info as "const xgrammar::TokenizerInfo*",
max_threads as "int",
cache_enabled as "bool",
max_memory_bytes as "long long"
] -> GrammarCompiler as "xgrammar::GrammarCompiler" {
return xgrammar::GrammarCompiler(
*tokenizer_info,
max_threads,
cache_enabled,
max_memory_bytes
);
});
grammar_compiler
}
/// Compile a Grammar object into a CompiledGrammar.
///
/// This method takes a Grammar object (which can be created from EBNF, JSON schema,
/// regex, or structural tags) and compiles it for use with a GrammarMatcher.
///
/// # Arguments
/// * `grammar` - The grammar to compile
///
/// # Returns
/// * `Ok(CompiledGrammar)` - A compiled grammar that can be used with GrammarMatcher
/// * `Err(XGrammarErr)` - Error if the grammar compilation fails
///
/// # Errors
/// * Returns error if the grammar is invalid or compilation fails
///
/// # Example
/// ```
/// # use xgrammar::{Grammar, GrammarCompiler, TokenizerInfo};
/// # fn example(tokenizer_info: &TokenizerInfo) -> xgrammar::Result<()> {
/// let compiler = GrammarCompiler::new(tokenizer_info);
/// let grammar = Grammar::builtin_json_grammar();
/// let compiled = compiler.compile_grammar(&grammar)?;
/// # Ok(())
/// # }
/// ```
pub fn compile_grammar(&self, grammar: &Grammar) -> Result<CompiledGrammar> {
let result = cpp!(unsafe [
self as "xgrammar::GrammarCompiler*",
grammar as "const xgrammar::Grammar*"
] -> CompiledGrammarResult as "CompiledGrammarResult" {
try {
auto compiled = self->CompileGrammar(*grammar);
return {true, compiled, nullptr, 0};
} catch (const std::exception& e) {
return {false, CompiledGrammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Compile a grammar for standard JSON format.
///
/// This is a convenience method that returns a compiled grammar for parsing
/// any valid JSON without schema constraints.
///
/// # Returns
/// * `Ok(CompiledGrammar)` - A compiled grammar that matches standard JSON format
/// * `Err(XGrammarErr)` - Error if the grammar compilation fails
///
/// # Errors
/// * Returns error if the builtin JSON grammar compilation fails (unlikely)
///
/// # Example
/// ```
/// # use xgrammar::{GrammarCompiler, TokenizerInfo};
/// # fn example(tokenizer_info: &TokenizerInfo) -> xgrammar::Result<()> {
/// let compiler = GrammarCompiler::new(tokenizer_info);
/// let compiled = compiler.compile_builtin_json_grammar()?;
/// # Ok(())
/// # }
/// ```
pub fn compile_builtin_json_grammar(&self) -> Result<CompiledGrammar> {
let result = cpp!(unsafe [self as "xgrammar::GrammarCompiler*"] -> CompiledGrammarResult as "CompiledGrammarResult" {
try {
auto compiled = self->CompileBuiltinJSONGrammar();
return {true, compiled, nullptr, 0};
} catch (const std::exception& e) {
return {false, CompiledGrammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Compile a grammar from a JSON schema string.
///
/// This method compiles a JSON schema specification into a grammar that enforces
/// the schema constraints during text generation.
///
/// # Arguments
/// * `schema` - The JSON schema string to compile
/// * `any_whitespace` - Whether to allow flexible whitespace in the JSON output. None uses true
/// * `indent` - Number of spaces for indentation. None means no indentation
/// * `separators` - Custom separators as (object_separator, array_separator), e.g., (":", ","). None uses default separators
/// * `strict_mode` - Whether to enforce strict JSON schema validation. None uses true
/// * `max_whitespace_cnt` - Maximum number of consecutive whitespace characters allowed. None means no limit
///
/// # Returns
/// * `Ok(CompiledGrammar)` - A compiled grammar that can be used with GrammarMatcher
/// * `Err(XGrammarErr)` - Error if the JSON schema is invalid or compilation fails
///
/// # Errors
/// * Returns error if the JSON schema is invalid
/// * Returns error if the schema cannot be compiled
///
/// # Example
/// ```
/// # use xgrammar::{GrammarCompiler, TokenizerInfo};
/// # fn example(tokenizer_info: &TokenizerInfo) -> xgrammar::Result<()> {
/// let compiler = GrammarCompiler::new(tokenizer_info);
/// let schema = r#"{"type": "object", "properties": {"name": {"type": "string"}}}"#;
/// let compiled = compiler.compile_json_schema(schema, None, None, None, None, None)?;
/// # Ok(())
/// # }
/// ```
pub fn compile_json_schema(
&self,
schema: &str,
any_whitespace: Option<bool>,
indent: Option<i32>,
separators: Option<(String, String)>,
strict_mode: Option<bool>,
max_whitespace_cnt: Option<i32>,
) -> Result<CompiledGrammar> {
let schema_ptr = schema.as_ptr();
let schema_len = schema.len();
let any_whitespace = any_whitespace.unwrap_or(true);
let strict_mode = strict_mode.unwrap_or(true);
let has_indent = indent.is_some();
let indent_value = indent.unwrap_or(0);
let has_separators = separators.is_some();
let has_max_whitespace_cnt = max_whitespace_cnt.is_some();
let max_whitespace_cnt_value = max_whitespace_cnt.unwrap_or(0);
// `separators` stays alive for the whole call, so borrowing is fine.
let (obj_sep, array_sep) = match &separators {
Some((obj_sep, array_sep)) => (obj_sep.as_str(), array_sep.as_str()),
None => ("", ""),
};
let obj_sep_ptr = obj_sep.as_ptr();
let obj_sep_len = obj_sep.len();
let array_sep_ptr = array_sep.as_ptr();
let array_sep_len = array_sep.len();
let result = cpp!(unsafe [
self as "xgrammar::GrammarCompiler*",
schema_ptr as "const uint8_t*",
schema_len as "size_t",
any_whitespace as "bool",
has_indent as "bool",
indent_value as "int",
has_separators as "bool",
obj_sep_ptr as "const uint8_t*",
obj_sep_len as "size_t",
array_sep_ptr as "const uint8_t*",
array_sep_len as "size_t",
strict_mode as "bool",
has_max_whitespace_cnt as "bool",
max_whitespace_cnt_value as "int"
] -> CompiledGrammarResult as "CompiledGrammarResult" {
try {
std::string schema_str(reinterpret_cast<const char*>(schema_ptr), schema_len);
std::optional<int> opt_indent = has_indent ? std::make_optional(indent_value) : std::nullopt;
std::optional<std::pair<std::string, std::string>> opt_separators;
if (has_separators) {
opt_separators = std::make_pair(
std::string(reinterpret_cast<const char*>(obj_sep_ptr), obj_sep_len),
std::string(reinterpret_cast<const char*>(array_sep_ptr), array_sep_len)
);
} else {
opt_separators = std::nullopt;
}
std::optional<int> opt_max_whitespace_cnt = has_max_whitespace_cnt ? std::make_optional(max_whitespace_cnt_value) : std::nullopt;
auto compiled = self->CompileJSONSchema(schema_str, any_whitespace, opt_indent, opt_separators, strict_mode, opt_max_whitespace_cnt);
return {true, compiled, nullptr, 0};
} catch (const std::exception& e) {
return {false, CompiledGrammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Compile a grammar from a regular expression pattern.
///
/// This method compiles a regex pattern into a grammar that matches text
/// conforming to the specified pattern.
///
/// # Arguments
/// * `regex` - The regex pattern string to compile
///
/// # Returns
/// * `Ok(CompiledGrammar)` - A compiled grammar that can be used with GrammarMatcher
/// * `Err(XGrammarErr)` - Error if the regex pattern is invalid or compilation fails
///
/// # Errors
/// * Returns error if the regex pattern is invalid
/// * Returns error if the regex cannot be compiled
///
/// # Example
/// ```
/// # use xgrammar::{GrammarCompiler, TokenizerInfo};
/// # fn example(tokenizer_info: &TokenizerInfo) -> xgrammar::Result<()> {
/// let compiler = GrammarCompiler::new(tokenizer_info);
/// let compiled = compiler.compile_regex(r"[a-z]+@[a-z]+\.[a-z]+")?;
/// # Ok(())
/// # }
/// ```
pub fn compile_regex(&self, regex: &str) -> Result<CompiledGrammar> {
let regex_ptr = regex.as_ptr();
let regex_len = regex.len();
let result = cpp!(unsafe [
self as "xgrammar::GrammarCompiler*",
regex_ptr as "const uint8_t*",
regex_len as "size_t"
] -> CompiledGrammarResult as "CompiledGrammarResult" {
try {
std::string regex_str(reinterpret_cast<const char*>(regex_ptr), regex_len);
auto compiled = self->CompileRegex(regex_str);
return {true, compiled, nullptr, 0};
} catch (const std::exception& e) {
return {false, CompiledGrammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Clear the internal cache of compiled grammars.
/// This frees up memory used by cached compiled grammars.
pub fn clear_cache(&self) {
cpp!(unsafe [self as "xgrammar::GrammarCompiler*"] {
self->ClearCache();
})
}
/// Return the approximate memory usage of the compiler cache in bytes.
///
/// # Returns
/// * The current cache size in bytes
pub fn get_cache_size_bytes(&self) -> i64 {
cpp!(unsafe [self as "const xgrammar::GrammarCompiler*"] -> i64 as "long long" {
return self->GetCacheSizeBytes();
})
}
/// Return the cache limit in bytes. -1 means unlimited.
///
/// # Returns
/// * The cache limit in bytes, or -1 for unlimited
pub fn cache_limit_bytes(&self) -> i64 {
cpp!(unsafe [self as "const xgrammar::GrammarCompiler*"] -> i64 as "long long" {
return self->CacheLimitBytes();
})
}
/// Compile a grammar from a structural tag JSON string.
///
/// This method compiles a structural tag specification provided as a JSON string into
/// a grammar that can be used with a GrammarMatcher. The structural tag allows for
/// structured text generation with specific formatting tags and schemas.
///
/// # Arguments
/// * `structural_tag_json` - A JSON string specifying the structural tag configuration.
/// The JSON should contain the structural tag items and triggers.
///
/// # Returns
/// * `Ok(CompiledGrammar)` - A compiled grammar that can be used with GrammarMatcher
/// * `Err(XGrammarErr)` - Error if the structural tag is invalid or compilation fails
///
/// # Errors
/// * Returns error if the structural tag JSON is invalid
/// * Returns error if the structural tag cannot be compiled
///
/// # Example
/// ```no_run
/// # use xgrammar::{GrammarCompiler, TokenizerInfo};
/// # fn example(tokenizer_info: &TokenizerInfo) -> xgrammar::Result<()> {
/// let compiler = GrammarCompiler::new(tokenizer_info);
/// let structural_tag_json = r#"{"tags": [{"begin": "<start>", "schema": "{}", "end": "</start>"}], "triggers": ["trigger1"]}"#;
/// let compiled_grammar = compiler.compile_structural_tag(structural_tag_json)?;
/// # Ok(())
/// # }
/// ```
pub fn compile_structural_tag(&self, structural_tag_json: &str) -> Result<CompiledGrammar> {
let structural_tag_json_ptr = structural_tag_json.as_ptr();
let structural_tag_json_len = structural_tag_json.len();
let result = cpp!(unsafe [
self as "xgrammar::GrammarCompiler*",
structural_tag_json_ptr as "const uint8_t*",
structural_tag_json_len as "size_t"
] -> CompiledGrammarResult as "CompiledGrammarResult" {
try {
std::string structural_tag_json_str(
reinterpret_cast<const char*>(structural_tag_json_ptr),
structural_tag_json_len
);
auto compiled = self->CompileStructuralTag(structural_tag_json_str);
return {true, compiled, nullptr, 0};
} catch (const std::exception& e) {
return {false, CompiledGrammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
}
/// Represents a context-free grammar for grammar-guided text generation.
///
/// The Grammar struct supports Extended Backus-Naur Form (EBNF) grammar specifications
/// following the GBNF specification from llama.cpp. It provides flexible grammar generation
/// and manipulation for constrained text generation tasks.
///
/// # Construction Methods
///
/// Grammar can be constructed from various sources:
/// - [`Grammar::from_ebnf`]: From EBNF grammar strings
/// - [`Grammar::from_json_schema`]: From JSON schema specifications
/// - [`Grammar::from_regex`]: From regular expression patterns
/// - [`Grammar::from_structural_tag`]: From structural tags with embedded schemas
/// - [`Grammar::builtin_json_grammar`]: Standard JSON grammar
///
/// # Grammar Operations
///
/// Multiple grammars can be combined using:
/// - [`Grammar::union`]: Creates a grammar matching any of the input grammars (equivalent to `|` operator)
/// - [`Grammar::concat`]: Creates a grammar matching concatenated sequences (equivalent to `+` operator)
impl Grammar {
/// Construct a grammar from an EBNF-formatted string.
///
/// This method creates a context-free grammar from an Extended Backus-Naur Form (EBNF)
/// specification. The grammar follows the GBNF specification from llama.cpp.
///
/// # Arguments
/// * `ebnf_string` - The EBNF grammar specification string
/// * `root_rule_name` - The name of the root rule to use as the entry point. If None, uses "root"
///
/// # Returns
/// * `Ok(Grammar)` - A Grammar object constructed from the EBNF specification
/// * `Err(XGrammarErr)` - Error if the EBNF string is invalid or malformed
///
/// # Errors
/// * Returns error if the EBNF string contains syntax errors
/// * Returns error if the root rule is not defined
/// * Returns error if there are undefined rule references
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let ebnf = r#"
/// root ::= "Hello, " name "!"
/// name ::= [A-Z][a-z]+
/// "#;
/// let grammar = Grammar::from_ebnf(ebnf, Some("root")).unwrap();
/// assert!(!grammar.is_null());
///
/// // Invalid EBNF will return an error
/// let invalid_ebnf = r#"root ::= "unterminated string"#;
/// assert!(Grammar::from_ebnf(invalid_ebnf, Some("root")).is_err());
/// ```
pub fn from_ebnf(ebnf_string: &str, root_rule_name: Option<&str>) -> Result<Self> {
let ebnf_string_ptr = ebnf_string.as_ptr();
let ebnf_string_len = ebnf_string.len();
let root_rule_name = root_rule_name.unwrap_or("root");
let root_rule_name_ptr = root_rule_name.as_ptr();
let root_rule_name_len = root_rule_name.len();
let result = cpp!(unsafe [
ebnf_string_ptr as "const uint8_t*",
ebnf_string_len as "size_t",
root_rule_name_ptr as "const uint8_t*",
root_rule_name_len as "size_t"
] -> GrammarResult as "GrammarResult" {
try {
auto grammar = Grammar::FromEBNF(
string(reinterpret_cast<const char*>(ebnf_string_ptr), ebnf_string_len),
string(reinterpret_cast<const char*>(root_rule_name_ptr), root_rule_name_len)
);
return {true, grammar, nullptr, 0};
} catch (const std::exception& e) {
return {false, Grammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Construct a grammar from a JSON schema string.
///
/// This method creates a grammar from a JSON schema specification that enforces schema
/// constraints during text generation. The schema can be in JSON string format or
/// represent a Pydantic-style model structure.
///
/// # Arguments
/// * `schema` - The JSON schema string defining the structure to enforce
/// * `any_whitespace` - Whether to allow flexible whitespace in the JSON output. When true,
/// any amount of whitespace is allowed between tokens
/// * `indent` - Number of spaces for indentation in the JSON output. When specified,
/// produces formatted JSON with the given indentation level
/// * `separators` - Custom separators for JSON formatting as (item_separator, key_separator).
/// For example, `(":", ",")` produces compact JSON. When None, uses standard JSON separators
/// * `strict_mode` - Whether to enforce strict JSON schema validation. When true, ensures
/// all schema constraints are strictly enforced
/// * `max_whitespace_cnt` - Maximum number of consecutive whitespace characters allowed.
/// Useful for preventing excessive whitespace in generated output
/// * `print_converted_ebnf` - Whether to print the converted EBNF grammar for debugging purposes
///
/// # Returns
/// * `Ok(Grammar)` - A Grammar object that enforces the JSON schema constraints
/// * `Err(XGrammarErr)` - Error if the JSON schema is invalid or malformed
///
/// # Errors
/// * Returns error if the JSON schema is invalid
/// * Returns error if the schema cannot be converted to EBNF
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let schema = r#"{
/// "type": "object",
/// "properties": {
/// "name": {"type": "string"},
/// "age": {"type": "integer"}
/// },
/// "required": ["name", "age"]
/// }"#;
/// let grammar = Grammar::from_json_schema(
/// schema,
/// Some(true), // allow flexible whitespace
/// Some(2), // 2-space indentation
/// None, // default separators
/// Some(true), // strict mode
/// None, // no whitespace limit
/// Some(false) // don't print EBNF
/// ).unwrap();
/// assert!(!grammar.is_null());
///
/// // Invalid JSON schema will return an error
/// let invalid_schema = r#"{ invalid json }"#;
/// assert!(Grammar::from_json_schema(invalid_schema, None, None, None, None, None, None).is_err());
/// ```
pub fn from_json_schema(
schema: &str,
any_whitespace: Option<bool>,
indent: Option<i32>,
separators: Option<(String, String)>,
strict_mode: Option<bool>,
max_whitespace_cnt: Option<i32>,
print_converted_ebnf: Option<bool>,
) -> Result<Self> {
let schema_ptr = schema.as_ptr();
let schema_len = schema.len();
let any_whitespace = any_whitespace.unwrap_or(true);
let strict_mode = strict_mode.unwrap_or(true);
let print_converted_ebnf = print_converted_ebnf.unwrap_or(false);
let has_indent = indent.is_some();
let indent_value = indent.unwrap_or(0);
let has_separators = separators.is_some();
let has_max_whitespace_cnt = max_whitespace_cnt.is_some();
let max_whitespace_cnt_value = max_whitespace_cnt.unwrap_or(0);
// `separators` stays alive for the whole call, so borrowing is fine.
let (obj_sep, array_sep) = match &separators {
Some((obj_sep, array_sep)) => (obj_sep.as_str(), array_sep.as_str()),
None => ("", ""),
};
let obj_sep_ptr = obj_sep.as_ptr();
let obj_sep_len = obj_sep.len();
let array_sep_ptr = array_sep.as_ptr();
let array_sep_len = array_sep.len();
let result = cpp!(unsafe [
schema_ptr as "const uint8_t*",
schema_len as "size_t",
any_whitespace as "bool",
has_indent as "bool",
indent_value as "int",
has_separators as "bool",
obj_sep_ptr as "const uint8_t*",
obj_sep_len as "size_t",
array_sep_ptr as "const uint8_t*",
array_sep_len as "size_t",
strict_mode as "bool",
has_max_whitespace_cnt as "bool",
max_whitespace_cnt_value as "int",
print_converted_ebnf as "bool"
] -> GrammarResult as "GrammarResult" {
try {
std::string schema_str(reinterpret_cast<const char*>(schema_ptr), schema_len);
std::optional<int> opt_indent = has_indent ? std::make_optional(indent_value) : std::nullopt;
std::optional<std::pair<std::string, std::string>> opt_separators;
if (has_separators) {
opt_separators = std::make_pair(
std::string(reinterpret_cast<const char*>(obj_sep_ptr), obj_sep_len),
std::string(reinterpret_cast<const char*>(array_sep_ptr), array_sep_len)
);
} else {
opt_separators = std::nullopt;
}
std::optional<int> opt_max_whitespace_cnt = has_max_whitespace_cnt ? std::make_optional(max_whitespace_cnt_value) : std::nullopt;
auto grammar = Grammar::FromJSONSchema(
schema_str,
any_whitespace,
opt_indent,
opt_separators,
strict_mode,
opt_max_whitespace_cnt,
print_converted_ebnf
);
return {true, grammar, nullptr, 0};
} catch (const std::exception& e) {
return {false, Grammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Construct a grammar from a regular expression string.
///
/// This method creates a grammar by converting a regular expression pattern into
/// an EBNF grammar specification. The resulting grammar matches text conforming
/// to the specified regex pattern.
///
/// # Arguments
/// * `regex` - The regular expression pattern string to convert
/// * `print_converted_ebnf` - Whether to print the converted EBNF grammar for debugging purposes
///
/// # Returns
/// * `Ok(Grammar)` - A Grammar object that matches the regex pattern
/// * `Err(XGrammarErr)` - Error if the regex pattern is invalid or malformed
///
/// # Errors
/// * Returns error if the regex pattern is invalid
/// * Returns error if the regex cannot be converted to EBNF
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// // Match email-like patterns
/// let grammar = Grammar::from_regex(r"[a-z]+@[a-z]+\.[a-z]+", Some(false)).unwrap();
/// assert!(!grammar.is_null());
///
/// // Invalid regex will return an error
/// let invalid_regex = r"[";
/// assert!(Grammar::from_regex(invalid_regex, Some(false)).is_err());
/// ```
pub fn from_regex(regex: &str, print_converted_ebnf: Option<bool>) -> Result<Self> {
let regex_ptr = regex.as_ptr();
let regex_len = regex.len();
let print_converted_ebnf = print_converted_ebnf.unwrap_or(false);
let result = cpp!(unsafe [
regex_ptr as "const uint8_t*",
regex_len as "size_t",
print_converted_ebnf as "bool"
] -> GrammarResult as "GrammarResult" {
try {
auto grammar = Grammar::FromRegex(
string(reinterpret_cast<const char*>(regex_ptr), regex_len),
print_converted_ebnf
);
return {true, grammar, nullptr, 0};
} catch (const std::exception& e) {
return {false, Grammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Construct a grammar from a structural tag JSON string.
///
/// This method creates a grammar from structural tags that enable grammar-guided generation
/// with specific formatting markers. Structural tags are useful for dispatching between
/// different grammars based on trigger tokens and wrapping content with specific begin/end tags.
///
/// The structural tag format supports:
/// - Single tag specification with begin marker, JSON schema, and end marker
/// - Multiple tags for grammar dispatching based on triggers
/// - Legacy tag/trigger pattern support
///
/// # Arguments
/// * `structural_tag_json` - A JSON string specifying the structural tag configuration.
/// The JSON should contain structural tag items with `begin`, `schema`, and `end` fields,
/// and optionally `triggers` for grammar dispatching.
/// * `tokenizer_info` - Optional `TokenizerInfo` for resolving string token references.
/// Required when the structural tag JSON uses token-level formats introduced in
/// xgrammar >= 0.1.33 (e.g. `type: "token"`, `type: "exclude_token"`,
/// `type: "any_tokens"`, `type: "token_triggered_tags"`). Pass `None` for pure
/// character-level tag formats.
///
/// # Returns
/// * `Ok(Grammar)` if the JSON is valid and the grammar was successfully created
/// * `Err(XGrammarErr)` if the JSON is invalid or the structural tag is malformed
///
/// # Errors
/// * [`XGrammarErr::InvalidJson`] if the input is not valid JSON
/// * [`XGrammarErr::InvalidStructuralTag`] if the structural tag specification is invalid
/// * [`XGrammarErr::InvalidGrammar`] if converting an embedded sub-grammar
/// fails (e.g. an invalid `regex` pattern, `grammar` EBNF, or
/// `json_schema`) — upstream raises these as untyped errors, not typed
/// `XGrammarError`s
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// use serde_json::json;
///
/// // Triggered tags example for tool calling with multiple functions
/// let structural_tag = json!({
/// "format": {
/// "type": "triggered_tags",
/// "triggers": ["<function="],
/// "tags": [
/// {
/// "begin": "<function=get_weather>",
/// "content": {
/// "type": "json_schema",
/// "json_schema": {
/// "type": "object",
/// "properties": {
/// "city": {"type": "string"},
/// "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
/// },
/// "required": ["city"]
/// }
/// },
/// "end": "</function>"
/// }
/// ]
/// }
/// });
///
/// let grammar = Grammar::from_structural_tag(&structural_tag.to_string(), None).unwrap();
/// assert!(!grammar.is_null());
/// ```
pub fn from_structural_tag(
structural_tag_json: &str,
tokenizer_info: Option<&TokenizerInfo>,
) -> Result<Self> {
let structural_tag_json_ptr = structural_tag_json.as_ptr();
let structural_tag_json_len = structural_tag_json.len();
let tokenizer_info_ptr: *const TokenizerInfo =
tokenizer_info.map(|t| t as *const TokenizerInfo).unwrap_or(std::ptr::null());
let result = cpp!(unsafe [
structural_tag_json_ptr as "const uint8_t*",
structural_tag_json_len as "size_t",
tokenizer_info_ptr as "const xgrammar::TokenizerInfo*"
] -> GrammarResult as "GrammarResult" {
try {
std::string structural_tag_json_str(
reinterpret_cast<const char*>(structural_tag_json_ptr),
structural_tag_json_len
);
std::optional<xgrammar::TokenizerInfo> opt_tokenizer_info;
if (tokenizer_info_ptr != nullptr) {
opt_tokenizer_info = *tokenizer_info_ptr;
}
auto result = xgrammar::Grammar::FromStructuralTag(
structural_tag_json_str, opt_tokenizer_info
);
// Check if result holds a Grammar or an error
if (std::holds_alternative<xgrammar::Grammar>(result)) {
return {true, std::get<xgrammar::Grammar>(result), nullptr, 0};
} else {
int32_t error_kind = kXGOtherError;
char* error_message = xgr_dup_variant_error(
std::get<xgrammar::StructuralTagError>(result), &error_kind
);
return {false, Grammar(NullObj()), error_message, error_kind};
}
} catch (const std::exception& e) {
// Converting an embedded sub-grammar (regex/EBNF/JSON schema)
// throws untyped errors instead of returning the variant; the
// exception must not cross the FFI boundary.
return {false, Grammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Get a grammar for standard JSON format.
///
/// This method returns a pre-built grammar that matches any valid JSON according
/// to the JSON specification, without schema constraints. It's useful as a starting
/// point for JSON generation or when you need to accept any valid JSON structure.
///
/// # Returns
/// * A Grammar object that matches standard JSON format
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let json_grammar = Grammar::builtin_json_grammar();
/// assert!(!json_grammar.is_null());
/// ```
pub fn builtin_json_grammar() -> Self {
cpp!(unsafe [] -> Grammar as "xgrammar::Grammar" {
return xgrammar::Grammar::BuiltinJSONGrammar();
})
}
/// Create a grammar that matches any of the provided grammars.
///
/// This method combines multiple grammars using a union operation, creating a new grammar
/// that accepts input matching any of the input grammars. This is equivalent to the `|`
/// (OR) operator in regular expressions.
///
/// # Arguments
/// * `grammars` - A slice of Grammar objects to combine
///
/// # Returns
/// * A new Grammar that matches if any of the input grammars match
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let grammar1 = Grammar::from_regex(r"[0-9]+", Some(false)).unwrap();
/// let grammar2 = Grammar::from_regex(r"[a-z]+", Some(false)).unwrap();
/// let union_grammar = Grammar::union(&[grammar1, grammar2]);
/// assert!(!union_grammar.is_null());
/// ```
pub fn union(grammars: &[Grammar]) -> Self {
let grammars_ptr = grammars.as_ptr();
let num_grammars = grammars.len();
cpp!(unsafe [
grammars_ptr as "const xgrammar::Grammar*",
num_grammars as "size_t"
] -> Grammar as "xgrammar::Grammar" {
std::vector<xgrammar::Grammar> grammars_vec;
grammars_vec.reserve(num_grammars);
for (size_t i = 0; i < num_grammars; ++i) {
grammars_vec.push_back(grammars_ptr[i]);
}
return xgrammar::Grammar::Union(grammars_vec);
})
}
/// Create a grammar that matches the concatenation of the provided grammars.
///
/// This method combines multiple grammars in sequence, creating a new grammar that requires
/// input to match all grammars in order. This is equivalent to the `+` (concatenation)
/// operator in formal language theory.
///
/// # Arguments
/// * `grammars` - A slice of Grammar objects to concatenate in order
///
/// # Returns
/// * A new Grammar that matches the sequential combination of all input grammars
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let greeting = Grammar::from_regex(r"Hello", Some(false)).unwrap();
/// let space = Grammar::from_regex(r" ", Some(false)).unwrap();
/// let name = Grammar::from_regex(r"[A-Z][a-z]+", Some(false)).unwrap();
/// let concat_grammar = Grammar::concat(&[greeting, space, name]);
/// assert!(!concat_grammar.is_null());
/// ```
pub fn concat(grammars: &[Grammar]) -> Self {
let grammars_ptr = grammars.as_ptr();
let num_grammars = grammars.len();
cpp!(unsafe [
grammars_ptr as "const xgrammar::Grammar*",
num_grammars as "size_t"
] -> Grammar as "xgrammar::Grammar" {
std::vector<xgrammar::Grammar> grammars_vec;
grammars_vec.reserve(num_grammars);
for (size_t i = 0; i < num_grammars; ++i) {
grammars_vec.push_back(grammars_ptr[i]);
}
return xgrammar::Grammar::Concat(grammars_vec);
})
}
/// Check if the grammar object is null.
///
/// A null grammar typically indicates an uninitialized or invalid grammar state.
/// This can occur when grammar construction fails or when working with default values.
///
/// # Returns
/// * `true` if the grammar is null (invalid/uninitialized)
/// * `false` if the grammar is valid
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let grammar = Grammar::builtin_json_grammar();
/// assert!(!grammar.is_null());
/// ```
pub fn is_null(&self) -> bool {
cpp!(unsafe [self as "const xgrammar::Grammar*"] -> bool as "bool" {
return self->IsNull();
})
}
/// Serialize the grammar to a JSON string.
///
/// The output is tied to xgrammar's internal serialization version (v14 as
/// of the vendored xgrammar v0.2.3): it can only be read back by
/// [`Self::deserialize_json`] of a build using the same serialization
/// version.
///
/// # Example
/// ```
/// # use xgrammar::Grammar;
/// let grammar = Grammar::builtin_json_grammar();
/// let json = grammar.serialize_json();
/// let restored = Grammar::deserialize_json(&json).unwrap();
/// assert_eq!(json, restored.serialize_json());
/// ```
pub fn serialize_json(&self) -> String {
let mut out = String::new();
let out_ptr = &mut out as *mut String;
cpp!(unsafe [self as "const xgrammar::Grammar*", out_ptr as "void*"] {
std::string json = self->SerializeJSON();
const uint8_t* data = reinterpret_cast<const uint8_t*>(json.data());
size_t len = json.size();
rust!(XGR_Grammar_Serialize_set [
out_ptr: *mut String as "void*",
data: *const u8 as "const uint8_t*",
len: usize as "size_t"
] {
// SAFETY: `data`/`len` point into the C++ std::string, which
// outlives this call; `out_ptr` comes from a live `&mut String`.
unsafe { crate::assign_utf8_lossy(out_ptr, data, len) };
});
});
out
}
/// Deserialize a grammar from a JSON string produced by
/// [`Self::serialize_json`].
///
/// # Errors
/// * [`XGrammarErr::DeserializeVersion`] if the data was serialized by an
/// incompatible xgrammar serialization version
/// * [`XGrammarErr::InvalidJson`] if the input is not valid JSON
/// * [`XGrammarErr::DeserializeFormat`] if the JSON does not have the
/// expected structure
/// * [`XGrammarErr::InvalidGrammar`] if an untyped xgrammar error is
/// raised while reconstructing the grammar (e.g. valid JSON that is not
/// an object)
pub fn deserialize_json(json: &str) -> Result<Self> {
// Marshal as pointer + length (not CString): the input is untrusted,
// and an interior NUL byte must surface as a deserialization error
// rather than a panic.
let json_ptr = json.as_ptr();
let json_len = json.len();
let result = cpp!(unsafe [
json_ptr as "const uint8_t*",
json_len as "size_t"
] -> GrammarResult as "GrammarResult" {
try {
std::string json_str(reinterpret_cast<const char*>(json_ptr), json_len);
auto result = xgrammar::Grammar::DeserializeJSON(json_str);
if (std::holds_alternative<xgrammar::Grammar>(result)) {
return {true, std::get<xgrammar::Grammar>(result), nullptr, 0};
} else {
int32_t error_kind = kXGOtherError;
char* error_message = xgr_dup_variant_error(
std::get<xgrammar::SerializationError>(result), &error_kind
);
return {false, Grammar(NullObj()), error_message, error_kind};
}
} catch (const std::exception& e) {
return {false, Grammar(NullObj()), strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
}
/// Return the number of `i32` elements required per token-bitmask row for a
/// vocabulary of `vocab_size` tokens, i.e. `ceil(vocab_size / 32)`.
///
/// Use this to size the bitmask tensors passed to
/// [`GrammarMatcher::fill_next_token_bitmask`],
/// [`GrammarMatcher::traverse_draft_tree`] and
/// [`BatchGrammarMatcher::batch_fill_next_token_bitmask`]. Mirrors the
/// upstream free function `xgrammar::GetBitmaskSize`.
///
/// # Panics
/// Panics if `vocab_size` is not positive: the upstream computation is plain
/// C++ `int` arithmetic, so a non-positive or near-`i32::MAX` input would
/// wrap or overflow instead of failing.
pub fn get_bitmask_size(vocab_size: i32) -> i32 {
assert!(
(1..=i32::MAX - 31).contains(&vocab_size),
"vocab_size must be in 1..={} (got {vocab_size})",
i32::MAX - 31
);
cpp!(unsafe [vocab_size as "int"] -> i32 as "int32_t" {
return xgrammar::GetBitmaskSize(vocab_size);
})
}
impl GrammarMatcher {
/// Create a GrammarMatcher from a compiled grammar.
/// # Arguments
/// * `compiled_grammar` - The compiled grammar to use
pub fn new(compiled_grammar: &CompiledGrammar) -> Self {
Self::with(compiled_grammar, None, Some(true), None)
}
/// Create a GrammarMatcher from a compiled grammar.
/// # Arguments
/// * `compiled_grammar` - The compiled grammar to use
/// * `override_stop_tokens` - Optional list of token ids to override the default stop tokens
/// * `terminate_without_stop_token` - Whether to terminate the matcher without accepting a stop token.
/// * `max_rollback_tokens` - Deprecated. You don't need to set it and it's always unlimited (-1).
/// The new Earley parser significantly reduces the number of states, so we can allow
/// unlimited rollback. The maximum number of rollback tokens allowed. The rollback operation
/// is useful for jump-forward decoding and speculative decoding.
pub fn with(
compiled_grammar: &CompiledGrammar,
override_stop_tokens: Option<&[i32]>,
terminate_without_stop_token: Option<bool>,
max_rollback_tokens: Option<i32>,
) -> Self {
// Keep it sync with the C++ implementation:
// https://github.com/mlc-ai/xgrammar/blob/95bdfce011506ea95306b37d080115a2da3e369a/cpp/grammar_matcher.cc#L257
let terminate_without_stop_token = terminate_without_stop_token.unwrap_or(false);
let max_rollback_tokens = max_rollback_tokens.unwrap_or(0);
let override_stop_tokens_ptr =
override_stop_tokens.as_ref().map_or(std::ptr::null(), |v| v.as_ptr());
let override_stop_tokens_len = override_stop_tokens.as_ref().map_or(0, |v| v.len());
cpp!(unsafe [
compiled_grammar as "const xgrammar::CompiledGrammar*",
override_stop_tokens_ptr as "const int32_t*",
override_stop_tokens_len as "size_t",
terminate_without_stop_token as "bool",
max_rollback_tokens as "int"
] -> GrammarMatcher as "xgrammar::GrammarMatcher" {
std::optional<std::vector<int32_t>> opt_override_stop_tokens;
if (override_stop_tokens_len > 0) {
opt_override_stop_tokens = std::vector<int32_t>(
override_stop_tokens_ptr,
override_stop_tokens_ptr + override_stop_tokens_len
);
} else {
opt_override_stop_tokens = std::nullopt;
}
return xgrammar::GrammarMatcher(
*compiled_grammar,
opt_override_stop_tokens,
terminate_without_stop_token,
max_rollback_tokens
);
})
}
/// Accept one token and update the state of the matcher.
///
/// # Arguments
/// * `token_id` - The id of the token to accept.
/// * `debug_print` - If true, print debug information.
///
/// # Returns
/// * Whether the token is accepted.
///
/// # Note
/// Termination state.
///
/// When the end of the root rule is reached, the matcher can only accept the stop token.
/// The matcher is terminated after accepting the stop token, i.e. no AcceptToken or
/// FindNextTokenMask operations can be performed. The termination state can be canceled
/// using rollback().
pub fn accept_token(&mut self, token_id: i32, debug_print: Option<bool>) -> bool {
let debug_print = debug_print.unwrap_or(false);
cpp!(unsafe [self as "xgrammar::GrammarMatcher*", token_id as "int32_t", debug_print as "bool"] -> bool as "bool" {
return self->AcceptToken(token_id, debug_print);
})
}
/// Accept a string and update the state of the matcher. The whole string is considered
/// as one step in rollback. It is used to complement the functionality of `accept_token()`,
/// and `accept_token()` should always be used to accept tokens.
///
/// # Arguments
/// * `input_str` - The string to be accepted.
/// * `debug_print` - Whether to print information about the internal state of the matcher.
///
/// # Returns
/// * Whether the string is accepted.
pub fn accept_string(&mut self, input_str: &str, debug_print: Option<bool>) -> bool {
let debug_print = debug_print.unwrap_or(false);
let input_str_ptr = input_str.as_ptr();
let input_str_len = input_str.len();
cpp!(unsafe [
self as "xgrammar::GrammarMatcher*",
input_str_ptr as "const uint8_t*",
input_str_len as "size_t",
debug_print as "bool"
] -> bool as "bool" {
return self->AcceptString(
std::string(reinterpret_cast<const char*>(input_str_ptr), input_str_len),
debug_print
);
})
}
/// Get the set of tokens that are acceptable for the next step and store them in a bitmask.
///
/// # Arguments
/// * `next_token_bitmask` - The bitmask to store the result. The bitmask must be pre-allocated
/// a DLTensor with shape (tokenizer.GetVocabSize() + 31) / 32 (see [`get_bitmask_size`]),
/// and dtype int32.
/// * `index` - The index of the bitmask to fill. If None, the first bitmask is filled.
/// * `debug_print` - If true, print debug information.
///
/// # Returns
/// * `Ok(bool)` - Whether the bitmask need to be applied (not all-true).
/// * `Err(XGrammarErr)` - Error if the operation fails (e.g., matcher terminated, invalid bitmask).
///
/// # Errors
/// * Returns error if the matcher has terminated after accepting the stop token
/// * Returns error if the bitmask has invalid dtype, shape, or device type
pub fn fill_next_token_bitmask(
&mut self,
next_token_bitmask: &mut DLTensor,
index: Option<usize>,
debug_print: Option<bool>,
) -> Result<bool> {
ensure_row_major_contiguous(next_token_bitmask, "next_token_bitmask")?;
let dl_tensor = next_token_bitmask.dl_tensor();
let index = index.unwrap_or(0) as i32;
let debug_print = debug_print.unwrap_or(false);
let result = cpp!(unsafe [self as "xgrammar::GrammarMatcher*", dl_tensor as "DLTensor*", index as "int32_t", debug_print as "bool"] -> MatcherResult as "MatcherResult" {
try {
bool value = self->FillNextTokenBitmask(dl_tensor, index, debug_print);
return {true, value, nullptr, 0};
} catch (const std::exception& e) {
return {false, false, strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Traverse a draft token tree (speculative decoding) and fill one token
/// bitmask row per tree node.
///
/// Performs a depth-first traversal of the draft tree: row `i` of
/// `token_bitmask` receives the set of tokens allowed *after* accepting the
/// draft tokens on the path from the root to node `i`. Verifying an entire
/// draft tree this way is much cheaper than replaying
/// [`Self::accept_token`] / [`Self::fill_next_token_bitmask`] /
/// [`Self::rollback`] per node from Rust.
///
/// # Tree encoding
/// Node `0` is the root. `retrieve_next_token[i]` is the index of node
/// `i`'s first child, or `-1` if it has none; `retrieve_next_sibling[i]` is
/// the index of node `i`'s next sibling, or `-1`. The root must not have a
/// sibling (`retrieve_next_sibling[0] == -1`).
///
/// `draft_tokens[0]` is ignored: the root token is the one already produced
/// by the target model, so its acceptance is not re-checked and row `0` is
/// simply the bitmask of the matcher's current state.
///
/// # Arguments
/// * `retrieve_next_token` - 1-D `int64` CPU tensor of first-child indices.
/// * `retrieve_next_sibling` - 1-D `int64` CPU tensor of sibling indices.
/// * `draft_tokens` - 1-D `int64` CPU tensor of the draft token id at each node.
/// All three tensors must have the same length `N >= 1` (the node count).
/// * `token_bitmask` - Pre-allocated 2-D `int32` CPU tensor of shape
/// `(N, get_bitmask_size(vocab_size))` (see [`get_bitmask_size`]).
/// * `time_threshold` - Maximum traversal time in seconds; `None` (or a
/// value `<= 0`) disables the timeout.
///
/// # Node semantics
/// A draft token that is out of vocabulary range or not permitted by its
/// parent's bitmask row gets its row zeroed, and its **entire subtree is
/// skipped without touching the descendants' rows** (they keep whatever the
/// caller pre-filled). A node whose acceptance *terminates* the matcher is
/// treated the same way: its row is zeroed (no token may follow) and its
/// subtree is skipped. In both cases traversal continues with the node's
/// siblings and still returns `Ok(true)`. Calling this method on an
/// already-terminated matcher likewise returns `Ok(true)` with row `0`
/// zeroed and every other row untouched (unlike
/// [`Self::fill_next_token_bitmask`], which returns an error).
///
/// The C++ traversal recurses once per tree level, so extremely deep
/// chains (hundreds of thousands of nodes in a line) can exhaust the
/// native stack.
///
/// # Returns
/// * `Ok(true)` - The traversal completed. The matcher state is unchanged
/// (internal accepts and rollbacks cancel out).
/// * `Ok(false)` - The traversal timed out. Row `0` is always filled (the
/// timeout is only checked at non-root nodes) and unvisited rows keep
/// whatever the caller pre-filled, but the matcher state is fully
/// restored — every internal accept is rolled back as the failure
/// propagates — so the matcher can keep being used without a
/// [`Self::reset`].
/// * `Err(XGrammarErr::MatcherError)` - A tensor has an invalid dtype,
/// shape, or device type, is not row-major contiguous, or the tree
/// encoding is invalid (a link outside `[-1, N)`, or a node reachable
/// more than once).
///
/// # Example
/// ```no_run
/// # use xgrammar::{get_bitmask_size, CompiledGrammar, GrammarMatcher};
/// # use dlpark::versioned::SafeManagedTensorVersioned;
/// # use ndarray::{ArrayD, IxDyn};
/// # fn example(compiled: &CompiledGrammar) -> xgrammar::Result<()> {
/// let mut matcher = GrammarMatcher::new(compiled);
/// let vocab_size = compiled.get_tokenizer_info().get_vocab_size();
/// let bitmask_len = get_bitmask_size(vocab_size) as usize;
///
/// // Linear draft tree: node 0 (root) -> node 1 -> node 2.
/// let next_token = SafeManagedTensorVersioned::new(vec![1i64, 2, -1]).unwrap();
/// let next_sibling = SafeManagedTensorVersioned::new(vec![-1i64, -1, -1]).unwrap();
/// let draft_tokens = SafeManagedTensorVersioned::new(vec![0i64, 123, 456]).unwrap();
/// let bitmask = ArrayD::from_elem(IxDyn(&[3, bitmask_len]), 0i32);
/// let mut bitmask = SafeManagedTensorVersioned::new(bitmask).unwrap();
///
/// let completed = matcher.traverse_draft_tree(
/// &next_token, &next_sibling, &draft_tokens, &mut bitmask, None,
/// )?;
/// assert!(completed);
/// # Ok(())
/// # }
/// ```
pub fn traverse_draft_tree(
&mut self,
retrieve_next_token: &DLTensor,
retrieve_next_sibling: &DLTensor,
draft_tokens: &DLTensor,
token_bitmask: &mut DLTensor,
time_threshold: Option<f64>,
) -> Result<bool> {
ensure_row_major_contiguous(retrieve_next_token, "retrieve_next_token")?;
ensure_row_major_contiguous(retrieve_next_sibling, "retrieve_next_sibling")?;
ensure_row_major_contiguous(draft_tokens, "draft_tokens")?;
ensure_row_major_contiguous(token_bitmask, "token_bitmask")?;
// The C++ traversal follows the tree links without any bounds or
// cycle check, so validate them here. Tensors that are not 1-D int64
// CPU are skipped: the C++ side rejects those itself before touching
// any link.
if let (Some(next_token), Some(next_sibling)) =
(as_cpu_i64_slice(retrieve_next_token), as_cpu_i64_slice(retrieve_next_sibling))
&& next_token.len() == next_sibling.len()
{
validate_draft_tree(next_token, next_sibling)?;
}
let retrieve_next_token = retrieve_next_token.dl_tensor();
let retrieve_next_sibling = retrieve_next_sibling.dl_tensor();
let draft_tokens = draft_tokens.dl_tensor();
let token_bitmask = token_bitmask.dl_tensor();
let time_threshold = time_threshold.unwrap_or(-1.0);
let result = cpp!(unsafe [
self as "xgrammar::GrammarMatcher*",
retrieve_next_token as "const DLTensor*",
retrieve_next_sibling as "const DLTensor*",
draft_tokens as "const DLTensor*",
token_bitmask as "DLTensor*",
time_threshold as "double"
] -> MatcherResult as "MatcherResult" {
try {
bool value = self->TraverseDraftTree(
retrieve_next_token,
retrieve_next_sibling,
draft_tokens,
token_bitmask,
time_threshold
);
return {true, value, nullptr, 0};
} catch (const std::exception& e) {
return {false, false, strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Rollback the matcher to a previous state.
///
/// # Arguments
/// * `num_tokens` - The number of tokens to rollback. It cannot exceed the current number of
/// steps, nor can it exceed the specified maximum number of rollback tokens.
///
/// # Returns
/// * `Ok(())` - If the rollback succeeds
/// * `Err(XGrammarErr)` - Error if the rollback fails (e.g., num_tokens exceeds history)
///
/// # Errors
/// * Returns error if num_tokens exceeds the number of saved history steps
pub fn rollback(&mut self, num_tokens: Option<i32>) -> Result<()> {
let num_tokens = num_tokens.unwrap_or(1);
let result = cpp!(unsafe [self as "xgrammar::GrammarMatcher*", num_tokens as "int"] -> MatcherResult as "MatcherResult" {
try {
self->Rollback(num_tokens);
return {true, false, nullptr, 0};
} catch (const std::exception& e) {
return {false, false, strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Check if the matcher has accepted the stop token and terminated.
pub fn is_terminated(&self) -> bool {
cpp!(unsafe [self as "const xgrammar::GrammarMatcher*"] -> bool as "bool" {
return self->IsTerminated();
})
}
/// Check if the grammar's root rule has been fully matched by the input
/// accepted so far. Unlike [`Self::is_terminated`], this does not require the
/// stop token to have been accepted.
pub fn is_completed(&self) -> bool {
cpp!(unsafe [self as "const xgrammar::GrammarMatcher*"] -> bool as "bool" {
return self->IsCompleted();
})
}
/// Get the maximum number of rollback tokens allowed.
pub fn get_max_rollback_tokens(&self) -> i32 {
cpp!(unsafe [self as "const xgrammar::GrammarMatcher*"] -> i32 as "int" {
return self->GetMaxRollbackTokens();
})
}
pub fn get_stop_token_ids(&self) -> Vec<i32> {
// Avoid relying on layout-compatibility between `Vec<T>` and
// `std::vector<T>` — the two have different memory layouts on
// libstdc++. Push each element into a Rust-allocated `Vec<i32>` via
// the `rust!` callback bridge so we never assume layout parity.
let mut out: Vec<i32> = Vec::new();
let out_ptr = &mut out as *mut Vec<i32>;
cpp!(unsafe [
self as "const xgrammar::GrammarMatcher*",
out_ptr as "void*"
] {
const auto& ids = self->GetStopTokenIds();
for (int id : ids) {
rust!(XGR_Matcher_StopTokenIds_push [
out_ptr: *mut Vec<i32> as "void*",
id: i32 as "int"
] {
// SAFETY: `out_ptr` was obtained from a live `&mut Vec<i32>`
// on the Rust side and is only used within this call.
unsafe { (*out_ptr).push(id) };
});
}
});
out
}
/// Reset the matcher to the initial state.
pub fn reset(&mut self) {
cpp!(unsafe [self as "xgrammar::GrammarMatcher*"] {
self->Reset();
})
}
/// Deep-copy the matcher state. The returned matcher shares the
/// `CompiledGrammar` and `TokenizerInfo` with `self` (cheap shared_ptr
/// aliases) but has independent matcher state that evolves separately.
/// Useful for speculative decoding and branching search — accepting tokens
/// on the forked matcher does not affect `self`.
///
/// This is unrelated to POSIX `fork(2)` despite the name; the name mirrors
/// upstream xgrammar's `GrammarMatcher::Fork()`.
pub fn fork(&self) -> GrammarMatcher {
cpp!(unsafe [self as "const xgrammar::GrammarMatcher*"]
-> GrammarMatcher as "xgrammar::GrammarMatcher"
{
return self->Fork();
})
}
}
/// Batched helpers that operate on a slice of [`GrammarMatcher`].
///
/// ## When an instance is (and isn't) needed
///
/// The method layout intentionally mirrors upstream xgrammar, where only one batch
/// operation actually runs in parallel:
///
/// - [`Self::batch_fill_next_token_bitmask`] — **instance method** (`&mut self`).
/// Uses the thread pool owned by this `BatchGrammarMatcher` to fan the per-matcher
/// work out across threads. Construct once via [`Self::new`] or
/// [`Self::with_max_threads`] and reuse.
///
/// - [`Self::batch_accept_token`], [`Self::batch_accept_string`],
/// [`Self::batch_rollback`] — **associated (static) functions**.
/// Upstream implements these as a plain sequential `for` loop; no thread pool
/// or instance state is involved, so there is nothing for `self` to carry.
/// Call them as `BatchGrammarMatcher::batch_accept_token(...)` without
/// constructing an instance.
///
/// This asymmetry reflects the upstream implementation
/// (`BatchGrammarMatcher::Impl::BatchAcceptToken` etc. in
/// `thirdparty/xgrammar/cpp/grammar_matcher.cc`). If upstream ever parallelizes
/// those ops, they will gain a `&self` receiver here.
impl BatchGrammarMatcher {
/// Create a `BatchGrammarMatcher` with the default `"auto"` thread policy
/// (roughly half of the available hardware threads).
///
/// The constructed instance owns a thread pool that is used **only** by
/// [`Self::batch_fill_next_token_bitmask`]. If you do not call that method,
/// you do not need a `BatchGrammarMatcher` instance — the other batch helpers
/// are associated functions.
///
/// Equivalent to `BatchGrammarMatcher::default()` (the `Default` impl is
/// auto-generated by `cpp_class!` and maps to the C++ default constructor,
/// which also uses `"auto"`).
pub fn new() -> Self {
cpp!(unsafe [] -> BatchGrammarMatcher as "xgrammar::BatchGrammarMatcher" {
return xgrammar::BatchGrammarMatcher(std::string("auto"));
})
}
/// Create a `BatchGrammarMatcher` with an explicit maximum thread count for
/// the thread pool used by [`Self::batch_fill_next_token_bitmask`].
///
/// A value of `1` disables parallelism (the work runs on the calling thread).
/// Values `> 1` spin up a thread pool on each `batch_fill_next_token_bitmask`
/// call (upstream rebuilds the pool each call because `ThreadPool` is not
/// reusable after `Join`).
pub fn with_max_threads(max_threads: i32) -> Self {
cpp!(unsafe [max_threads as "int32_t"]
-> BatchGrammarMatcher as "xgrammar::BatchGrammarMatcher"
{
return xgrammar::BatchGrammarMatcher(max_threads);
})
}
/// Batched version of [`GrammarMatcher::fill_next_token_bitmask`].
///
/// This is the **only** batch method that uses the thread pool of this
/// `BatchGrammarMatcher`; it therefore takes `&mut self`. When
/// `max_threads > 1` the per-matcher bitmask fills are executed in parallel.
///
/// # Arguments
/// * `matchers` - The matchers to operate on in parallel. Mutated in place.
/// * `next_token_bitmask` - Pre-allocated `DLTensor` with shape `(N, bitmask_len)` and
/// dtype `int32`, where `N >= matchers.len()` and `bitmask_len` is the per-matcher
/// bitmask length.
/// * `indices` - Optional mapping from matcher index to bitmask row. If `None`, the
/// bitmask row `i` is written for `matchers[i]`.
/// * `debug_print` - When `true`, print debug information.
pub fn batch_fill_next_token_bitmask(
&mut self,
matchers: &mut [GrammarMatcher],
next_token_bitmask: &mut DLTensor,
indices: Option<&[i32]>,
debug_print: Option<bool>,
) -> Result<()> {
ensure_row_major_contiguous(next_token_bitmask, "next_token_bitmask")?;
let dl_tensor = next_token_bitmask.dl_tensor();
let debug_print = debug_print.unwrap_or(false);
let matchers_ptr = matchers.as_mut_ptr();
let num_matchers = matchers.len();
let indices_ptr = indices.map(|s| s.as_ptr()).unwrap_or(std::ptr::null());
let num_indices = indices.map(|s| s.len()).unwrap_or(0);
let has_indices = indices.is_some();
// xgrammar's batch API takes `std::vector<GrammarMatcher>*`, so we must
// materialize a vector over our Rust-owned slice. This is cheap and safe
// because `GrammarMatcher` is a shared_ptr<Impl> PIMPL (see upstream
// xgrammar/object.h `XGRAMMAR_DEFINE_PIMPL_METHODS`): copying a matcher
// clones the shared_ptr, so `matchers_vec[i]` aliases the *same* Impl as
// `matchers_ptr[i]`. Batch ops mutate through `pimpl_`, so state changes
// land in the shared Impl and are visible to the caller without any
// write-back step.
let result = cpp!(unsafe [
self as "xgrammar::BatchGrammarMatcher*",
matchers_ptr as "xgrammar::GrammarMatcher*",
num_matchers as "size_t",
dl_tensor as "DLTensor*",
indices_ptr as "const int32_t*",
num_indices as "size_t",
has_indices as "bool",
debug_print as "bool"
] -> MatcherResult as "MatcherResult" {
try {
std::vector<xgrammar::GrammarMatcher> matchers_vec(
matchers_ptr, matchers_ptr + num_matchers
);
std::optional<std::vector<int32_t>> opt_indices;
if (has_indices) {
opt_indices = std::vector<int32_t>(
indices_ptr, indices_ptr + num_indices
);
}
self->BatchFillNextTokenBitmask(
&matchers_vec, dl_tensor, opt_indices, debug_print
);
return {true, false, nullptr, 0};
} catch (const std::exception& e) {
return {false, false, strdup(e.what()), xgr_error_kind(e)};
}
});
result.into()
}
/// Batched version of [`GrammarMatcher::accept_token`]. Returns a vector of
/// booleans indicating whether each token was accepted by the corresponding
/// matcher.
///
/// This is an **associated function**, not a method — upstream xgrammar
/// implements it as a sequential `for` loop with no thread pool, so no
/// `BatchGrammarMatcher` instance is required. Call as
/// `BatchGrammarMatcher::batch_accept_token(&mut matchers, &token_ids, None)`.
pub fn batch_accept_token(
matchers: &mut [GrammarMatcher],
token_ids: &[i32],
debug_print: Option<bool>,
) -> Vec<bool> {
let debug_print = debug_print.unwrap_or(false);
let matchers_ptr = matchers.as_mut_ptr();
let num_matchers = matchers.len();
let token_ids_ptr = token_ids.as_ptr();
let num_tokens = token_ids.len();
let mut out_buf = vec![0u8; num_matchers];
let out_ptr = out_buf.as_mut_ptr();
// See `batch_fill_next_token_bitmask` for why no write-back is needed:
// `GrammarMatcher` is a shared_ptr<Impl> PIMPL, so the vector entries
// alias the same Impl as the caller's slice.
cpp!(unsafe [
matchers_ptr as "xgrammar::GrammarMatcher*",
num_matchers as "size_t",
token_ids_ptr as "const int32_t*",
num_tokens as "size_t",
out_ptr as "uint8_t*",
debug_print as "bool"
] {
std::vector<xgrammar::GrammarMatcher> matchers_vec(
matchers_ptr, matchers_ptr + num_matchers
);
std::vector<int32_t> token_ids_vec(token_ids_ptr, token_ids_ptr + num_tokens);
auto out = xgrammar::BatchGrammarMatcher::BatchAcceptToken(
&matchers_vec, token_ids_vec, debug_print
);
size_t n = out.size() < num_matchers ? out.size() : num_matchers;
for (size_t i = 0; i < n; ++i) {
out_ptr[i] = out[i];
}
});
out_buf.into_iter().map(|b| b != 0).collect()
}
/// Batched version of [`GrammarMatcher::accept_string`]. Returns a vector of
/// booleans indicating whether each string was accepted by the corresponding
/// matcher.
///
/// This is an **associated function**, not a method — upstream xgrammar
/// implements it as a sequential `for` loop with no thread pool, so no
/// `BatchGrammarMatcher` instance is required. Call as
/// `BatchGrammarMatcher::batch_accept_string(&mut matchers, &input_strs, None)`.
pub fn batch_accept_string(
matchers: &mut [GrammarMatcher],
input_strs: &[&str],
debug_print: Option<bool>,
) -> Vec<bool> {
let debug_print = debug_print.unwrap_or(false);
let matchers_ptr = matchers.as_mut_ptr();
let num_matchers = matchers.len();
// Marshal each string as pointer + length (not CString): inputs may
// legally contain NUL bytes, which upstream's std::string preserves.
let str_ptrs: Vec<*const u8> = input_strs.iter().map(|s| s.as_ptr()).collect();
let str_lens: Vec<usize> = input_strs.iter().map(|s| s.len()).collect();
let str_ptrs_ptr = str_ptrs.as_ptr();
let str_lens_ptr = str_lens.as_ptr();
let num_strs = input_strs.len();
let mut out_buf = vec![0u8; num_matchers];
let out_ptr = out_buf.as_mut_ptr();
// See `batch_fill_next_token_bitmask` for why no write-back is needed:
// `GrammarMatcher` is a shared_ptr<Impl> PIMPL, so the vector entries
// alias the same Impl as the caller's slice.
cpp!(unsafe [
matchers_ptr as "xgrammar::GrammarMatcher*",
num_matchers as "size_t",
str_ptrs_ptr as "const uint8_t* const*",
str_lens_ptr as "const size_t*",
num_strs as "size_t",
out_ptr as "uint8_t*",
debug_print as "bool"
] {
std::vector<xgrammar::GrammarMatcher> matchers_vec(
matchers_ptr, matchers_ptr + num_matchers
);
std::vector<std::string> input_strs_vec;
input_strs_vec.reserve(num_strs);
for (size_t i = 0; i < num_strs; ++i) {
input_strs_vec.emplace_back(
reinterpret_cast<const char*>(str_ptrs_ptr[i]), str_lens_ptr[i]
);
}
auto out = xgrammar::BatchGrammarMatcher::BatchAcceptString(
&matchers_vec, input_strs_vec, debug_print
);
size_t n = out.size() < num_matchers ? out.size() : num_matchers;
for (size_t i = 0; i < n; ++i) {
out_ptr[i] = out[i];
}
});
// Anchor the backing storage here — after the `cpp!` block — so NLL
// cannot drop `str_ptrs` or `str_lens` while C++ still holds pointers
// derived from them. The `let _` binding is a use-site that extends
// both values' liveness to this line.
let _keep_alive = (&str_ptrs, &str_lens);
out_buf.into_iter().map(|b| b != 0).collect()
}
/// Batched version of [`GrammarMatcher::rollback`]. Each matcher rolls back
/// by the corresponding count in `num_tokens`.
///
/// This is an **associated function**, not a method — upstream xgrammar
/// implements it as a sequential `for` loop with no thread pool, so no
/// `BatchGrammarMatcher` instance is required. Call as
/// `BatchGrammarMatcher::batch_rollback(&mut matchers, &counts)`.
pub fn batch_rollback(matchers: &mut [GrammarMatcher], num_tokens: &[i32]) {
let matchers_ptr = matchers.as_mut_ptr();
let num_matchers = matchers.len();
let num_tokens_ptr = num_tokens.as_ptr();
let num_tokens_len = num_tokens.len();
// See `batch_fill_next_token_bitmask` for why no write-back is needed:
// `GrammarMatcher` is a shared_ptr<Impl> PIMPL, so the vector entries
// alias the same Impl as the caller's slice.
cpp!(unsafe [
matchers_ptr as "xgrammar::GrammarMatcher*",
num_matchers as "size_t",
num_tokens_ptr as "const int*",
num_tokens_len as "size_t"
] {
std::vector<xgrammar::GrammarMatcher> matchers_vec(
matchers_ptr, matchers_ptr + num_matchers
);
std::vector<int> num_tokens_vec(num_tokens_ptr, num_tokens_ptr + num_tokens_len);
xgrammar::BatchGrammarMatcher::BatchRollback(&matchers_vec, num_tokens_vec);
});
}
}