yeptris 0.6.22.1 → 0.6.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b02278afda0c454592a7e6b4d95fb4649960c08a4b7e3ad7e5260467333edc57
4
- data.tar.gz: c554fd944064c8b691003b83d9e3a6adc8500a04865dcf6c4fa28db1ba320db5
3
+ metadata.gz: 5f01bfddd4ec21b8d43466d6f64d35046317b841700e5d573a28a725de165dad
4
+ data.tar.gz: 5d034659ef3795f60f78825973e03ec27b23e9a5c81562c79c1d0cce11966ad9
5
5
  SHA512:
6
- metadata.gz: 7a1ed5aae840b006a5e2bb6ed27d8a1292969cb14b7963be92bd625f8ac0957ae974de91f73f11f8f642375eda5f773b747c21ee51479473742f7ff323f3b72a
7
- data.tar.gz: c21b92bec887dce27696762deb7818d30243647775e6113d6ccd4a2d07c759153be042f66f764c962afab3092bb30faacf265c51a1ccb5812ff07c89c8f918a4
6
+ metadata.gz: ae82c14ad74878148c3cb588c260a934a21e1aaf72dcc4a46225a27d28702da6130c4c7f2d7ca42a9993781045614b3defc4e955e87073daf760163c0b5c90a8
7
+ data.tar.gz: 4baa24664660581caee8f27a4280621574f32d5f3652ed793ef0041411fe898590fcd70ccdce665b4d3262e38070604aed7be034a3fcd5111cc41a9015a3a84c
data/lib/yeptris.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Yeptris
4
4
  # The gem's version lives in the parent namespace's file — the last
5
5
  # internal require (yeptris/version) retired with it.
6
- VERSION = "0.6.22.1".freeze
6
+ VERSION = "0.6.23.1".freeze
7
7
 
8
8
  # Informational notices are OPT-IN (yeptris-ruby#217): consumers
9
9
  # embed yeptris inside CLIs that assert clean stderr, and a
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.22
6
+ VERSION 0.6.23
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -28,8 +28,13 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
28
28
  case '-':
29
29
  case '?':
30
30
  /* indicators only before a blank or EOL: "-5" and "?x" are
31
- * plain (libyaml emits negative numbers plain; Psych, too) */
32
- return !(len == 1 || is_blank(p[1]));
31
+ * plain (libyaml emits negative numbers plain; Psych, too) —
32
+ * but fall through: the document-marker check below owns
33
+ * the bare "---"/"..." (libyaml quotes those) */
34
+ if (len == 1 || is_blank(p[1])) {
35
+ return 0;
36
+ }
37
+ break;
33
38
  case ',':
34
39
  case '[':
35
40
  case ']':
@@ -50,20 +55,67 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
50
55
  default:
51
56
  break;
52
57
  }
53
- for (uint32_t i = 0; i < len; i++) {
54
- char c = p[i];
55
- if (is_break(c) || c == '\t') {
56
- return 0;
57
- }
58
- if (c == ':') {
59
- if (i + 1 >= len || is_blank(p[i + 1])) {
58
+ /* The interior scan in SWAR words: every reject condition is a
59
+ * byte-class adjacency, so one masked word test is exact —
60
+ * break/tab bytes reject directly; ':' rejects with a blank at its
61
+ * NEXT lane (or the span's EOL); '#' with a blank at its PREVIOUS
62
+ * lane (the span's first byte is exempt — the head switch already
63
+ * handled a leading '#'). Cross-word adjacency rides one carried
64
+ * byte per boundary; the tail runs the byte loop, whose rules read
65
+ * real neighbors and need no carries. */
66
+ {
67
+ const uint64_t ONES = 0x0101010101010101ull;
68
+ const uint64_t HIGH = 0x8080808080808080ull;
69
+ #define YST_ZDET(w, ch) \
70
+ ((((w) ^ (uint64_t)(ch) * ONES) - ONES) & ~((w) ^ (uint64_t)(ch) * ONES) & HIGH)
71
+ uint32_t i = 0;
72
+ int prev_blank7 = 0;
73
+ while (i + 8 <= len) {
74
+ uint64_t w;
75
+ memcpy(&w, p + i, 8);
76
+ uint64_t space = YST_ZDET(w, ' ');
77
+ uint64_t tab = YST_ZDET(w, '\t');
78
+ uint64_t blank = space | tab;
79
+ uint64_t colon = YST_ZDET(w, ':');
80
+ uint64_t hash = YST_ZDET(w, '#');
81
+ /* tab and the breaks reject outright */
82
+ if (tab | YST_ZDET(w, '\n') | YST_ZDET(w, '\r')) {
60
83
  return 0;
61
84
  }
85
+ /* ':' + blank next: (blank >> 8) pulls lane i+1's flag down
86
+ * into lane i; the word's last lane takes the peeked byte
87
+ * (EOL at the span end counts as blank — the rule rejects
88
+ * there) */
89
+ if (colon) {
90
+ int next_blank = i + 8 < len ? is_blank(p[i + 8]) : 1;
91
+ if (colon & ((blank >> 8) | (next_blank ? (uint64_t)0x80 << 56 : 0))) {
92
+ return 0;
93
+ }
94
+ }
95
+ /* '#' + blank prev: (blank << 8) lifts lane i-1's flag up
96
+ * into lane i; lane 0 takes the carried last-lane flag */
97
+ if (hash && (hash & ((blank << 8) | (prev_blank7 ? 0x80 : 0)))) {
98
+ return 0;
99
+ }
100
+ prev_blank7 = (int)((blank >> 63) & 1); /* lane 7's flag is its top bit */
101
+ i += 8;
62
102
  }
63
- if (c == '#') {
64
- if (i > 0 && is_blank(p[i - 1])) {
103
+ #undef YST_ZDET
104
+ for (; i < len; i++) {
105
+ char c = p[i];
106
+ if (is_break(c) || c == '\t') {
65
107
  return 0;
66
108
  }
109
+ if (c == ':') {
110
+ if (i + 1 >= len || is_blank(p[i + 1])) {
111
+ return 0;
112
+ }
113
+ }
114
+ if (c == '#') {
115
+ if (i > 0 && is_blank(p[i - 1])) {
116
+ return 0;
117
+ }
118
+ }
67
119
  }
68
120
  }
69
121
  /* document markers */
@@ -10,6 +10,10 @@
10
10
 
11
11
  #include <stdint.h>
12
12
 
13
+ #ifdef __cplusplus
14
+ extern "C" {
15
+ #endif
16
+
13
17
  /* A plain scalar may be re-emitted plain only when every byte is safe
14
18
  * in ANY position of a block context: no leading/trailing space, no
15
19
  * indicator first byte, no ": " / " #" inside, no tab, no break, and
@@ -21,4 +25,8 @@ int yep_style_plain_safe(const char* p, uint32_t len);
21
25
  * (plain_safe plus: no ':' at all unless followed by a non-blank). */
22
26
  int yep_style_plain_key_safe(const char* p, uint32_t len);
23
27
 
28
+ #ifdef __cplusplus
29
+ }
30
+ #endif
31
+
24
32
  #endif /* YEP_EMIT_STYLE_H */
@@ -2457,13 +2457,12 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2457
2457
  }
2458
2458
  /* Whole-line fast path (TODO.restructure/54): the sink may build
2459
2459
  * the key and its classified value as one unit — no events. The
2460
- * value EVENT is prepared first (folded content, resolved alias,
2461
- * defined anchor: exactly what the chain below would emit), so a
2462
- * rejection emits key + prepared value unchanged. */
2463
- yep_event vv;
2464
- e_event_init(&vv, YEP_EV_SCALAR);
2465
- vv.line = e->line;
2466
- vv.col = e_col(e, sh->val_start) + 1;
2460
+ * value EVENT is assembled ONLY on the fallback path (the sinks
2461
+ * that take the pair read the yep_block_value and ignore it): the
2462
+ * semantic helpers run unchanged (alias resolution, anchor
2463
+ * definition, the fold), the fallback reuses the pair facts
2464
+ * captured before the fold — e_plain_multiline advances
2465
+ * line/pos across continuations, so post-fold e_col would lie. */
2467
2466
  /* the pair facts are captured BEFORE the fold: e_plain_multiline
2468
2467
  * advances line/pos across continuations */
2469
2468
  uint32_t pair_line = e->line;
@@ -2471,6 +2470,18 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2471
2470
  yep_block_value v;
2472
2471
  memset(&v, 0, sizeof(v));
2473
2472
  v.cls = (uint8_t)sh->val;
2473
+ yep_event vv; /* the fields the fallback reads are assigned HERE and
2474
+ * overwritten by the arms/helpers — MSVC C4701 cannot
2475
+ * see through the helpers' bypointer writes, so the
2476
+ * zero-init lives in this function, not a memset */
2477
+ vv.value.p = NULL;
2478
+ vv.value.len = 0;
2479
+ vv.borrowed = 0;
2480
+ vv.anchor.p = NULL;
2481
+ vv.anchor.len = 0;
2482
+ vv.anchor_id = 0;
2483
+ vv.end_line = 0;
2484
+ vv.end_col = 0;
2474
2485
  int prepared = 0;
2475
2486
  if (sh->val == YEP_LVAL_ALIAS) {
2476
2487
  e->pos = sh->val_start;
@@ -2497,8 +2508,6 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2497
2508
  vv.value.p = e->p + sh->val_span.start;
2498
2509
  vv.value.len = sh->val_span.end - sh->val_span.start;
2499
2510
  vv.borrowed = 1;
2500
- vv.style = YEP_STYLE_PLAIN;
2501
- vv.implicit = 1;
2502
2511
  e->pos = sh->val_span.end;
2503
2512
  if (e_plain_multiline(e, sh->val_span, key_col, &vv, 0) != 0) {
2504
2513
  return -1;
@@ -2523,9 +2532,30 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2523
2532
  return 1;
2524
2533
  }
2525
2534
  }
2535
+ /* Fallback: assemble the value event with the helper-filled
2536
+ * fields preserved — e_alias stamps type=ALIAS + the end marks,
2537
+ * e_plain_multiline stamps the fold end marks; the pair facts
2538
+ * taken pre-fold (post-fold e_col would read the advanced
2539
+ * line/pos). Field-for-field what the pre-fill built. */
2540
+ yep_event ve;
2541
+ e_event_init(&ve, YEP_EV_SCALAR);
2542
+ ve.line = pair_line;
2543
+ ve.col = (uint32_t)pair_val_col + 1;
2544
+ ve.value = vv.value;
2545
+ ve.borrowed = vv.borrowed;
2546
+ ve.anchor = vv.anchor;
2547
+ ve.anchor_id = vv.anchor_id;
2548
+ ve.end_line = vv.end_line;
2549
+ ve.end_col = vv.end_col;
2550
+ if (sh->val == YEP_LVAL_ALIAS) {
2551
+ ve.type = YEP_EV_ALIAS;
2552
+ } else {
2553
+ ve.style = YEP_STYLE_PLAIN;
2554
+ ve.implicit = 1;
2555
+ }
2526
2556
  yep_event kv;
2527
2557
  e_key_event(e, sh, key_col, pair_line, &kv);
2528
- if (emit_now(e, &kv) != 0 || emit_now(e, &vv) != 0) {
2558
+ if (emit_now(e, &kv) != 0 || emit_now(e, &ve) != 0) {
2529
2559
  return -2;
2530
2560
  }
2531
2561
  return 1;
@@ -601,6 +601,36 @@ reject:
601
601
  * (the classify scan carries no accept/reject structure) and
602
602
  * yeptris_tape_convert owns validation. Everything else matches
603
603
  * tape_walk state for state. */
604
+ /* LNUM_RUN — the initial digit run of a number span: SWAR words, eight
605
+ * bytes per pass, with the byte loop below as tail and classifier.
606
+ * digit iff 0x30-0x39: a lane flags when (c+0x46) carries into bit7
607
+ * (c > '9'), when ((c|0x80)-0xB0) goes negative (c < '0'), or when the
608
+ * byte is non-ASCII — and since the all-digit prefix never borrows
609
+ * across lanes, the flags are exact up to the first non-digit, where
610
+ * the scan stops and the byte loop classifies what follows (the
611
+ * consumed-only digits_only law is untouched: the run sets saw_digit,
612
+ * nothing else). */
613
+ #define LNUM_RUN \
614
+ do { \
615
+ size_t ks_ = k; \
616
+ while (k + 8 <= len) { \
617
+ uint64_t w_; \
618
+ memcpy(&w_, p + k, 8); \
619
+ uint64_t nd_ = \
620
+ ((w_ + 0x4646464646464646ull) & 0x8080808080808080ull) | \
621
+ (((w_ | 0x8080808080808080ull) - 0xB0B0B0B0B0B0B0B0ull) & 0x8080808080808080ull) | \
622
+ (w_ & 0x8080808080808080ull); \
623
+ if (nd_ != 0) { \
624
+ k += (size_t)yep_ctz64(nd_) >> 3; \
625
+ break; \
626
+ } \
627
+ k += 8; \
628
+ } \
629
+ if (k > ks_) { \
630
+ saw_digit = 1; \
631
+ } \
632
+ } while (0)
633
+
604
634
  /* The strict RFC 8259 number check over one number-ish run — the
605
635
  * in-walk float/exp validator (the out-of-line number_shape call +
606
636
  * rescan was 13% of json-doc; the digits-only case never gets here).
@@ -884,103 +914,132 @@ lmap1: /* {} closes here */
884
914
  goto lmapkey;
885
915
 
886
916
  lmap: /* a member MUST follow (after ','): JW_KEY — a closer rejects */
917
+ /* compact fast path: the byte is the key's quote already — LWS's
918
+ * loop would examine it once and exit */
919
+ if (i < len && p[i] == '"') {
920
+ goto lmapkey;
921
+ }
887
922
  LWS();
888
923
  if (p[i] != '"') {
889
924
  goto lreject;
890
925
  }
891
926
  lmapkey: { LSTR_SCAN(lmapcolon); }
892
927
  lmapcolon:
928
+ if (i < len && p[i] == ':') {
929
+ i++;
930
+ /* compact fast path: the value byte follows the colon with no
931
+ * whitespace — one load dispatches it (the ws test below is
932
+ * what LWS would run anyway) */
933
+ if (i < len) {
934
+ char v = p[i];
935
+ if (v != ' ' && v != '\n' && v != '\r' && (cl || v != '\t')) {
936
+ goto lmapval;
937
+ }
938
+ }
939
+ LWS();
940
+ goto lmapval;
941
+ }
893
942
  LWS();
894
943
  if (p[i] != ':') {
895
944
  goto lreject;
896
945
  }
897
946
  i++;
898
947
  LWS();
899
- {
900
- char c = p[i];
901
- if (c == '"') {
902
- LSTR_SCAN(lmapafter);
903
- }
904
- if ((unsigned)(c - '0') <= 9u || c == '-') {
905
- size_t k = i + 1;
906
- /* the digits-only tracking makes the pure-integer case a
907
- * 3-cycle leading-zero check — number_shape (a call + a
908
- * rescan, 13% of json-doc) runs only for dot/exp spans */
909
- int digits_only = 1, saw_digit = (c != '-');
910
- while (k < len) {
911
- char d = p[k];
912
- if ((unsigned)(d - '0') <= 9u) {
913
- k++;
914
- saw_digit = 1;
915
- continue;
916
- }
917
- /* the flag clears only on a CONSUMED byte: the run's
918
- * terminator (',', '}', ']', whitespace) must leave
919
- * digits_only intact, or every plain integer followed
920
- * by a delimiter falls into the full validator */
921
- if (d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') {
922
- digits_only = 0;
923
- k++;
924
- continue;
925
- }
926
- break;
948
+ lmapval: {
949
+ char c = p[i];
950
+ if (c == '"') {
951
+ LSTR_SCAN(lmapafter);
952
+ }
953
+ if ((unsigned)(c - '0') <= 9u || c == '-') {
954
+ size_t k = i + 1;
955
+ /* the digits-only tracking makes the pure-integer case a
956
+ * 3-cycle leading-zero check — number_shape (a call + a
957
+ * rescan, 13% of json-doc) runs only for dot/exp spans */
958
+ int digits_only = 1, saw_digit = (c != '-');
959
+ LNUM_RUN;
960
+ while (k < len) {
961
+ char d = p[k];
962
+ if ((unsigned)(d - '0') <= 9u) {
963
+ k++;
964
+ saw_digit = 1;
965
+ continue;
927
966
  }
928
- if (strict_nums) {
929
- int ok;
930
- if (digits_only && saw_digit) {
931
- size_t d0 = (c == '-') ? i + 1 : i;
932
- ok = (k - d0 == 1) || p[d0] != '0';
933
- } else {
934
- ok = tape_num_ok(p + i, (size_t)(k - i));
935
- }
936
- if (!ok) {
937
- goto lreject;
938
- }
967
+ /* the flag clears only on a CONSUMED byte: the run's
968
+ * terminator (',', '}', ']', whitespace) must leave
969
+ * digits_only intact, or every plain integer followed
970
+ * by a delimiter falls into the full validator */
971
+ if (d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') {
972
+ digits_only = 0;
973
+ k++;
974
+ continue;
939
975
  }
940
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
941
- (uint64_t)(YEP_T_NUM);
942
- count++;
943
- i = k;
944
- goto lmapafter;
976
+ break;
945
977
  }
946
- if (c == 't' || c == 'f' || c == 'n') {
947
- size_t wl = c == 'f' ? 5 : 4;
948
- if (i + wl > len) {
949
- goto lreject;
978
+ if (strict_nums) {
979
+ int ok;
980
+ if (digits_only && saw_digit) {
981
+ size_t d0 = (c == '-') ? i + 1 : i;
982
+ ok = (k - d0 == 1) || p[d0] != '0';
983
+ } else {
984
+ ok = tape_num_ok(p + i, (size_t)(k - i));
950
985
  }
951
- uint32_t got4;
952
- memcpy(&got4, p + i, 4);
953
- if (got4 != (c == 't' ? 0x65757274u /* "true" */
954
- : c == 'n' ? 0x6C6C756Eu /* "null" */
955
- : 0x736C6166u /* "fals" */) ||
956
- (c == 'f' && p[i + 4] != 'e')) {
986
+ if (!ok) {
957
987
  goto lreject;
958
988
  }
959
- if (i + wl < len) {
960
- char z = p[i + wl];
961
- if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
962
- z != '}' && z != ':') {
963
- goto lreject;
964
- }
965
- }
966
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
967
- (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
968
- count++;
969
- i += wl;
970
- goto lmapafter;
971
989
  }
972
- if (c == '{') {
973
- LPUSH(1);
974
- goto lmap1;
990
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
991
+ (uint64_t)(YEP_T_NUM);
992
+ count++;
993
+ i = k;
994
+ goto lmapafter;
995
+ }
996
+ if (c == 't' || c == 'f' || c == 'n') {
997
+ size_t wl = c == 'f' ? 5 : 4;
998
+ if (i + wl > len) {
999
+ goto lreject;
975
1000
  }
976
- if (c == '[') {
977
- LPUSH(0);
978
- goto lseq1;
1001
+ uint32_t got4;
1002
+ memcpy(&got4, p + i, 4);
1003
+ if (got4 != (c == 't' ? 0x65757274u /* "true" */
1004
+ : c == 'n' ? 0x6C6C756Eu /* "null" */
1005
+ : 0x736C6166u /* "fals" */) ||
1006
+ (c == 'f' && p[i + 4] != 'e')) {
1007
+ goto lreject;
979
1008
  }
980
- goto lreject;
1009
+ if (i + wl < len) {
1010
+ char z = p[i + wl];
1011
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
1012
+ z != '}' && z != ':') {
1013
+ goto lreject;
1014
+ }
1015
+ }
1016
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1017
+ (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1018
+ count++;
1019
+ i += wl;
1020
+ goto lmapafter;
981
1021
  }
1022
+ if (c == '{') {
1023
+ LPUSH(1);
1024
+ goto lmap1;
1025
+ }
1026
+ if (c == '[') {
1027
+ LPUSH(0);
1028
+ goto lseq1;
1029
+ }
1030
+ goto lreject;
1031
+ }
982
1032
 
983
1033
  lmapafter:
1034
+ /* compact fast path: value ',' key directly — the two LWS hops the
1035
+ * general form runs both exit on byte one here */
1036
+ if (i < len && p[i] == ',') {
1037
+ i++;
1038
+ if (i < len && p[i] == '"') {
1039
+ goto lmapkey;
1040
+ }
1041
+ goto lmap;
1042
+ }
984
1043
  LWS();
985
1044
  if (p[i] == ',') {
986
1045
  i++;
@@ -1009,6 +1068,7 @@ lseqval: {
1009
1068
  if ((unsigned)(c - '0') <= 9u || c == '-') {
1010
1069
  size_t k = i + 1;
1011
1070
  int digits_only = 1, saw_digit = (c != '-');
1071
+ LNUM_RUN;
1012
1072
  while (k < len) {
1013
1073
  char d = p[k];
1014
1074
  if ((unsigned)(d - '0') <= 9u) {
@@ -1080,6 +1140,18 @@ lseqval: {
1080
1140
  }
1081
1141
 
1082
1142
  lseqafter:
1143
+ /* compact fast path: value ',' value directly — both LWS hops exit
1144
+ * on byte one here */
1145
+ if (i < len && p[i] == ',') {
1146
+ i++;
1147
+ if (i < len) {
1148
+ char v = p[i];
1149
+ if (v != ' ' && v != '\n' && v != '\r' && (cl || v != '\t')) {
1150
+ goto lseqval;
1151
+ }
1152
+ }
1153
+ goto lseq;
1154
+ }
1083
1155
  LWS();
1084
1156
  if (p[i] == ',') {
1085
1157
  i++;
@@ -1092,6 +1164,7 @@ lseqafter:
1092
1164
 
1093
1165
  ldone:
1094
1166
  #undef LWS
1167
+ #undef LNUM_RUN
1095
1168
  #undef LPUSH
1096
1169
  #undef LCLOSE
1097
1170
  #undef LSTR_SCAN
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.22.1
4
+ version: 0.6.23.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-25 00:00:00.000000000 Z
11
+ date: 2026-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi