@scriptc/runtime 0.0.23 → 0.0.25

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.
package/src/scr_bytes.c CHANGED
@@ -7,6 +7,9 @@
7
7
  * encoding conversions (utf8 with WHATWG replacement, hex, base64) match
8
8
  * Node byte-for-byte — the differential corpus holds them to it. */
9
9
  #include "scr_runtime.h"
10
+ #ifdef SCR_TEXT_DECODER_LEGACY
11
+ #include "scr_text_decoder_data.h"
12
+ #endif
10
13
 
11
14
  #include <math.h>
12
15
  #include <stdio.h>
@@ -669,6 +672,16 @@ ScrStr *scr_strdec_end(const ScrStr *enc, double pending) {
669
672
  return scr_bytes_decode_utf8(pend, np);
670
673
  }
671
674
 
675
+ static void scr_bytes_to_str_bounds(const ScrBytes *b, double start, double end,
676
+ size_t *s0_out, size_t *e0_out) {
677
+ size_t len = b->len;
678
+ size_t s0 = start <= 0 ? 0 : ((size_t)start > len ? len : (size_t)start);
679
+ size_t e0 = end <= 0 ? 0 : ((size_t)end > len ? len : (size_t)end);
680
+ if (e0 < s0) e0 = s0;
681
+ *s0_out = s0;
682
+ *e0_out = e0;
683
+ }
684
+
672
685
  /* toString(enc, start, end): Node slices then decodes — start/end clamp
673
686
  * to [0, len] with start > end collapsing to empty and negative ends
674
687
  * clamping to 0 (Node's slice-then-decode; an OMITTED end never reaches
@@ -676,10 +689,8 @@ ScrStr *scr_strdec_end(const ScrStr *enc, double pending) {
676
689
  * form). Delegates to the whole-buffer decoder
677
690
  * through a stack view — no allocation, no rc traffic. */
678
691
  ScrStr *scr_bytes_to_str_range(const ScrBytes *b, const ScrStr *enc, double start, double end) {
679
- size_t len = b->len;
680
- size_t s0 = start <= 0 ? 0 : ((size_t)start > len ? len : (size_t)start);
681
- size_t e0 = end <= 0 ? 0 : ((size_t)end > len ? len : (size_t)end);
682
- if (e0 < s0) e0 = s0;
692
+ size_t s0, e0;
693
+ scr_bytes_to_str_bounds(b, start, end, &s0, &e0);
683
694
  ScrBytes view = { .rc = 1, .len = e0 - s0, .elem = b->elem, .data = b->data + s0, .backing = NULL };
684
695
  return scr_bytes_to_str(&view, enc);
685
696
  }
@@ -733,6 +744,559 @@ static size_t scr_bytes_put_cp(char *out, size_t o, uint32_t cp) {
733
744
  return o;
734
745
  }
735
746
 
747
+ /* ── WHATWG/Node TextDecoder legacy encodings ──────────────────────────
748
+ *
749
+ * The frontend only calls this entry with a compile-time encoding id. The
750
+ * lookup data is generated from the repository's pinned Node 24 oracle, so
751
+ * the resulting binary has no ICU/iconv dependency and behaves identically
752
+ * on Darwin, Linux, and Windows. UTF-8 keeps its older dedicated entry above:
753
+ * programs using only the default decoder do not retain these legacy tables.
754
+ */
755
+ #ifdef SCR_TEXT_DECODER_LEGACY
756
+
757
+ enum {
758
+ SCR_TD_X_USER_DEFINED = SCR_TD_SINGLE_COUNT,
759
+ SCR_TD_UTF16LE,
760
+ SCR_TD_UTF16BE,
761
+ SCR_TD_GB18030,
762
+ SCR_TD_BIG5,
763
+ SCR_TD_EUC_JP,
764
+ SCR_TD_ISO_2022_JP,
765
+ SCR_TD_SHIFT_JIS,
766
+ SCR_TD_EUC_KR,
767
+ };
768
+
769
+ typedef struct {
770
+ char *data;
771
+ size_t len;
772
+ } ScrTdOut;
773
+
774
+ static ScrTdOut scr_td_out_new(size_t input_len) {
775
+ if (input_len > (SIZE_MAX - 8) / 3) scr_bytes_oom();
776
+ ScrTdOut out = { malloc(input_len * 3 + 8), 0 };
777
+ if (!out.data) scr_bytes_oom();
778
+ return out;
779
+ }
780
+
781
+ static void scr_td_put(ScrTdOut *out, uint32_t cp) {
782
+ out->len = scr_bytes_put_cp(out->data, out->len, cp);
783
+ }
784
+
785
+ static void scr_td_error(ScrTdOut *out) {
786
+ scr_td_put(out, 0xfffd);
787
+ }
788
+
789
+ static ScrStr *scr_td_finish(ScrTdOut *out) {
790
+ ScrStr *str = scr_str_new(out->data, out->len);
791
+ free(out->data);
792
+ return str;
793
+ }
794
+
795
+ static ScrStr *scr_td_single_byte(const ScrBytes *b, unsigned encoding) {
796
+ ScrTdOut out = scr_td_out_new(b->len);
797
+ for (size_t i = 0; i < b->len; i++) {
798
+ uint8_t byte = b->data[i];
799
+ scr_td_put(&out, byte < 0x80 ? byte : scr_td_single[encoding][byte - 0x80]);
800
+ }
801
+ return scr_td_finish(&out);
802
+ }
803
+
804
+ static ScrStr *scr_td_x_user_defined(const ScrBytes *b) {
805
+ ScrTdOut out = scr_td_out_new(b->len);
806
+ for (size_t i = 0; i < b->len; i++) {
807
+ uint8_t byte = b->data[i];
808
+ scr_td_put(&out, byte < 0x80 ? byte : 0xf780 + byte - 0x80);
809
+ }
810
+ return scr_td_finish(&out);
811
+ }
812
+
813
+ static ScrStr *scr_td_utf16(const ScrBytes *b, bool be) {
814
+ const uint8_t *in = b->data;
815
+ size_t n = b->len;
816
+ /* TextDecoder's BOM handling strips only the BOM matching the selected
817
+ * endian decoder. The opposite BOM decodes to U+FFFE and remains. */
818
+ if (n >= 2 && ((!be && in[0] == 0xff && in[1] == 0xfe) ||
819
+ (be && in[0] == 0xfe && in[1] == 0xff))) {
820
+ in += 2;
821
+ n -= 2;
822
+ }
823
+ ScrTdOut out = scr_td_out_new(n);
824
+ size_t i = 0;
825
+ while (i + 1 < n) {
826
+ uint32_t cu = be ? ((uint32_t)in[i] << 8) | in[i + 1]
827
+ : (uint32_t)in[i] | ((uint32_t)in[i + 1] << 8);
828
+ i += 2;
829
+ if (cu >= 0xd800 && cu <= 0xdbff) {
830
+ if (i + 1 < n) {
831
+ uint32_t lo = be ? ((uint32_t)in[i] << 8) | in[i + 1]
832
+ : (uint32_t)in[i] | ((uint32_t)in[i + 1] << 8);
833
+ if (lo >= 0xdc00 && lo <= 0xdfff) {
834
+ i += 2;
835
+ scr_td_put(&out, 0x10000 + ((cu - 0xd800) << 10) + (lo - 0xdc00));
836
+ continue;
837
+ }
838
+ } else if (i < n) {
839
+ /* ICU treats a lead surrogate plus one final byte as one malformed
840
+ * UTF-16 subsequence. Consume that byte here so the odd-tail path
841
+ * below does not emit a second replacement. */
842
+ i++;
843
+ }
844
+ scr_td_error(&out);
845
+ continue;
846
+ }
847
+ if (cu >= 0xdc00 && cu <= 0xdfff) scr_td_error(&out);
848
+ else scr_td_put(&out, cu);
849
+ }
850
+ if (i < n) scr_td_error(&out); /* odd trailing byte */
851
+ return scr_td_finish(&out);
852
+ }
853
+
854
+ static uint32_t scr_td_gb_range(uint32_t pointer) {
855
+ size_t lo = 0;
856
+ size_t hi = sizeof scr_td_gb_ranges / sizeof scr_td_gb_ranges[0];
857
+ while (lo < hi) {
858
+ size_t mid = lo + (hi - lo) / 2;
859
+ const ScrTdGbRange *range = &scr_td_gb_ranges[mid];
860
+ if (pointer < range->start) hi = mid;
861
+ else if (pointer > range->end) lo = mid + 1;
862
+ else return range->code_point + pointer - range->start;
863
+ }
864
+ return 0;
865
+ }
866
+
867
+ /* A tiny prepend stack models the Encoding Standard's I/O queue restore.
868
+ * The gb18030 decoder is the only legacy decoder which can restore three
869
+ * bytes after discovering a malformed four-byte sequence. */
870
+ static ScrStr *scr_td_gb18030_decode(const ScrBytes *b) {
871
+ ScrTdOut out = scr_td_out_new(b->len);
872
+ uint8_t first = 0, second = 0, third = 0;
873
+ uint8_t replay[3];
874
+ size_t replay_len = 0;
875
+ size_t i = 0;
876
+ while (i < b->len || replay_len > 0) {
877
+ uint8_t byte = replay_len ? replay[--replay_len] : b->data[i++];
878
+ if (third) {
879
+ uint32_t cp = 0;
880
+ bool valid_fourth = byte >= 0x30 && byte <= 0x39;
881
+ if (valid_fourth) {
882
+ uint32_t pointer = (((uint32_t)(first - 0x81) * 10 + second - 0x30) * 126 +
883
+ third - 0x81) * 10 + byte - 0x30;
884
+ cp = scr_td_gb_range(pointer);
885
+ }
886
+ uint8_t old_second = second, old_third = third;
887
+ first = second = third = 0;
888
+ if (cp) {
889
+ scr_td_put(&out, cp);
890
+ } else {
891
+ scr_td_error(&out);
892
+ /* A structurally valid four-byte sequence with an unmapped pointer
893
+ * is consumed as one error. Only a malformed fourth byte restores
894
+ * the preceding payload bytes to Node's input queue. */
895
+ if (!valid_fourth) {
896
+ replay[replay_len++] = byte;
897
+ replay[replay_len++] = old_third;
898
+ replay[replay_len++] = old_second;
899
+ }
900
+ }
901
+ continue;
902
+ }
903
+ if (second) {
904
+ if (byte >= 0x81 && byte <= 0xfe) {
905
+ third = byte;
906
+ } else {
907
+ uint8_t old_second = second;
908
+ first = second = 0;
909
+ scr_td_error(&out);
910
+ replay[replay_len++] = byte;
911
+ replay[replay_len++] = old_second;
912
+ }
913
+ continue;
914
+ }
915
+ if (first) {
916
+ if (byte >= 0x30 && byte <= 0x39) {
917
+ second = byte;
918
+ continue;
919
+ }
920
+ uint8_t lead = first;
921
+ first = 0;
922
+ uint32_t cp = 0;
923
+ bool valid_trail = (byte >= 0x40 && byte <= 0x7e) ||
924
+ (byte >= 0x80 && byte <= 0xfe);
925
+ if (valid_trail) {
926
+ unsigned offset = byte < 0x7f ? 0x40 : 0x41;
927
+ cp = scr_td_gb18030[(lead - 0x81) * 190 + byte - offset];
928
+ }
929
+ if (cp) scr_td_put(&out, cp);
930
+ else {
931
+ scr_td_error(&out);
932
+ /* Non-ASCII invalid trails are consumed; ASCII is restored. */
933
+ if (!valid_trail && byte < 0x80) replay[replay_len++] = byte;
934
+ }
935
+ continue;
936
+ }
937
+ if (byte < 0x80) scr_td_put(&out, byte);
938
+ else if (byte == 0x80) scr_td_put(&out, 0x20ac);
939
+ else if (byte >= 0x81 && byte <= 0xfe) first = byte;
940
+ else scr_td_error(&out);
941
+ }
942
+ if (first || second || third) scr_td_error(&out);
943
+ return scr_td_finish(&out);
944
+ }
945
+
946
+ static ScrStr *scr_td_big5_decode(const ScrBytes *b) {
947
+ ScrTdOut out = scr_td_out_new(b->len);
948
+ uint8_t lead = 0;
949
+ for (size_t i = 0; i < b->len; i++) {
950
+ uint8_t byte = b->data[i];
951
+ if (lead) {
952
+ uint32_t cp = 0;
953
+ if ((byte >= 0x40 && byte <= 0x7e) || (byte >= 0xa1 && byte <= 0xfe)) {
954
+ unsigned offset = byte < 0x7f ? 0x40 : 0x62;
955
+ cp = scr_td_big5[(lead - 0x81) * 157 + byte - offset];
956
+ }
957
+ lead = 0;
958
+ if (cp) scr_td_put(&out, cp);
959
+ else {
960
+ scr_td_error(&out);
961
+ /* 0xff has a standalone ICU/Node mapping and is restored too. */
962
+ if (byte < 0x80 || byte == 0xff) i--;
963
+ }
964
+ continue;
965
+ }
966
+ if (byte < 0x80 || byte == 0x80) scr_td_put(&out, byte);
967
+ else if (byte >= 0x81 && byte <= 0xfe) lead = byte;
968
+ else if (byte == 0xff) scr_td_put(&out, 0xf8f8); /* ICU/Node mapping */
969
+ else scr_td_error(&out);
970
+ }
971
+ if (lead) scr_td_error(&out);
972
+ return scr_td_finish(&out);
973
+ }
974
+
975
+ static ScrStr *scr_td_euc_jp_decode(const ScrBytes *b) {
976
+ ScrTdOut out = scr_td_out_new(b->len);
977
+ uint8_t lead = 0;
978
+ bool jis0212 = false;
979
+ for (size_t i = 0; i < b->len; i++) {
980
+ uint8_t byte = b->data[i];
981
+ if (lead == 0x8e && byte >= 0xa1 && byte <= 0xdf) {
982
+ lead = 0;
983
+ scr_td_put(&out, 0xff61 + byte - 0xa1);
984
+ continue;
985
+ }
986
+ /* Node's pinned ICU EUC-JP converter exposes three NEC extensions just
987
+ * above the half-width katakana trail range. */
988
+ if (lead == 0x8e && byte >= 0xe0 && byte <= 0xe2) {
989
+ static const uint16_t extensions[] = { 0x00a2, 0x00a3, 0x00ac };
990
+ lead = 0;
991
+ scr_td_put(&out, extensions[byte - 0xe0]);
992
+ continue;
993
+ }
994
+ if (lead == 0x8f && byte >= 0xa1 && byte <= 0xfe) {
995
+ jis0212 = true;
996
+ lead = byte;
997
+ continue;
998
+ }
999
+ if (lead) {
1000
+ uint8_t old_lead = lead;
1001
+ lead = 0;
1002
+ uint32_t cp = 0;
1003
+ bool valid_trail = old_lead >= 0xa1 && old_lead <= 0xfe &&
1004
+ byte >= 0xa1 && byte <= 0xfe;
1005
+ if (valid_trail) {
1006
+ unsigned pointer = (old_lead - 0xa1) * 94 + byte - 0xa1;
1007
+ cp = jis0212 ? scr_td_jis0212[pointer] : scr_td_jis0208[pointer];
1008
+ }
1009
+ /* When the third byte of an 0x8f JIS-0212 sequence is malformed,
1010
+ * ICU reports the prefix and restores both following bytes. Model the
1011
+ * saved second byte as the next ordinary EUC-JP lead. */
1012
+ if (jis0212 && !valid_trail) {
1013
+ jis0212 = false;
1014
+ lead = old_lead;
1015
+ scr_td_error(&out);
1016
+ i--;
1017
+ continue;
1018
+ }
1019
+ jis0212 = false;
1020
+ if (cp) scr_td_put(&out, cp);
1021
+ else {
1022
+ scr_td_error(&out);
1023
+ /* C0/C1 bytes are restored; 0xa0 and 0xff are consumed with the
1024
+ * malformed sequence rather than producing a second error. The
1025
+ * 0x8e extension converter additionally restores 0xe5..0xfe. */
1026
+ if (byte < 0xa0 || (old_lead == 0x8e && byte >= 0xe5 && byte <= 0xfe)) i--;
1027
+ }
1028
+ continue;
1029
+ }
1030
+ if (byte < 0xa0 && byte != 0x8e && byte != 0x8f) scr_td_put(&out, byte);
1031
+ else if (byte == 0x8e || byte == 0x8f || (byte >= 0xa1 && byte <= 0xfe)) lead = byte;
1032
+ else scr_td_error(&out);
1033
+ }
1034
+ if (lead) scr_td_error(&out);
1035
+ return scr_td_finish(&out);
1036
+ }
1037
+
1038
+ static ScrStr *scr_td_shift_jis_decode(const ScrBytes *b) {
1039
+ ScrTdOut out = scr_td_out_new(b->len);
1040
+ uint8_t lead = 0;
1041
+ for (size_t i = 0; i < b->len; i++) {
1042
+ uint8_t byte = b->data[i];
1043
+ if (lead) {
1044
+ uint32_t cp = 0;
1045
+ bool valid_trail = (byte >= 0x40 && byte <= 0x7e) ||
1046
+ (byte >= 0x80 && byte <= 0xfc);
1047
+ if (valid_trail) {
1048
+ unsigned offset = byte < 0x7f ? 0x40 : 0x41;
1049
+ unsigned lead_offset = lead < 0xa0 ? 0x81 : 0xc1;
1050
+ unsigned pointer = (lead - lead_offset) * 188 + byte - offset;
1051
+ cp = scr_td_shift_jis[pointer];
1052
+ }
1053
+ lead = 0;
1054
+ if (cp) scr_td_put(&out, cp);
1055
+ else {
1056
+ scr_td_error(&out);
1057
+ if (!valid_trail) i--;
1058
+ }
1059
+ continue;
1060
+ }
1061
+ /* ICU's ibm-943_P15A-2003 converter (Node's Shift_JIS backend) has
1062
+ * three historical C0/DEL swaps which its TextDecoder exposes. */
1063
+ if (byte == 0x1a) scr_td_put(&out, 0x1c);
1064
+ else if (byte == 0x1c) scr_td_put(&out, 0x7f);
1065
+ else if (byte == 0x7f) scr_td_put(&out, 0x1a);
1066
+ else if (byte < 0x80) scr_td_put(&out, byte);
1067
+ else if (byte >= 0xa1 && byte <= 0xdf) scr_td_put(&out, 0xff61 + byte - 0xa1);
1068
+ else if ((byte >= 0x81 && byte <= 0x9f) || (byte >= 0xe0 && byte <= 0xfc)) lead = byte;
1069
+ else scr_td_error(&out);
1070
+ }
1071
+ if (lead) scr_td_error(&out);
1072
+ return scr_td_finish(&out);
1073
+ }
1074
+
1075
+ static ScrStr *scr_td_euc_kr_decode(const ScrBytes *b) {
1076
+ ScrTdOut out = scr_td_out_new(b->len);
1077
+ uint8_t lead = 0;
1078
+ for (size_t i = 0; i < b->len; i++) {
1079
+ uint8_t byte = b->data[i];
1080
+ if (lead) {
1081
+ uint32_t cp = 0;
1082
+ if (lead >= 0xa1 && lead <= 0xfe && byte >= 0xa1 && byte <= 0xfe) {
1083
+ cp = scr_td_euc_kr[(lead - 0xa1) * 94 + byte - 0xa1];
1084
+ }
1085
+ lead = 0;
1086
+ if (cp) scr_td_put(&out, cp);
1087
+ else {
1088
+ scr_td_error(&out);
1089
+ if (byte < 0xa0) i--;
1090
+ }
1091
+ continue;
1092
+ }
1093
+ if (byte < 0xa0 && byte != 0x8e && byte != 0x8f) scr_td_put(&out, byte);
1094
+ else if (byte >= 0xa1 && byte <= 0xfe) lead = byte;
1095
+ else scr_td_error(&out);
1096
+ }
1097
+ if (lead) scr_td_error(&out);
1098
+ return scr_td_finish(&out);
1099
+ }
1100
+
1101
+ enum ScrTdIsoState {
1102
+ SCR_TD_ISO_ASCII,
1103
+ SCR_TD_ISO_ROMAN,
1104
+ SCR_TD_ISO_KATAKANA,
1105
+ SCR_TD_ISO_LEAD,
1106
+ SCR_TD_ISO_TRAIL,
1107
+ SCR_TD_ISO_ESCAPE_START,
1108
+ SCR_TD_ISO_ESCAPE,
1109
+ };
1110
+
1111
+ static ScrStr *scr_td_iso_2022_jp_decode(const ScrBytes *b) {
1112
+ ScrTdOut out = scr_td_out_new(b->len);
1113
+ enum ScrTdIsoState state = SCR_TD_ISO_ASCII;
1114
+ enum ScrTdIsoState output_state = SCR_TD_ISO_ASCII;
1115
+ uint8_t lead = 0;
1116
+ int replay = -1;
1117
+ bool output_flag = false;
1118
+ size_t i = 0;
1119
+ bool at_eof = false;
1120
+ while (!at_eof) {
1121
+ int item;
1122
+ if (replay >= 0) {
1123
+ item = replay;
1124
+ replay = -1;
1125
+ } else {
1126
+ item = i < b->len ? b->data[i++] : -1;
1127
+ }
1128
+ uint8_t byte = item < 0 ? 0 : (uint8_t)item;
1129
+ /* ICU treats CR/LF as line boundaries while a non-ASCII designation is
1130
+ * active: emit the separator and resume in ASCII. A pending JIS lead is
1131
+ * different (TRAIL state below) — there the line break completes an
1132
+ * ill-formed pair and remains a replacement, matching Node. */
1133
+ if (item >= 0 && (byte == 0x0a || byte == 0x0d) &&
1134
+ (state == SCR_TD_ISO_KATAKANA || state == SCR_TD_ISO_LEAD)) {
1135
+ output_flag = false;
1136
+ state = output_state = SCR_TD_ISO_ASCII;
1137
+ scr_td_put(&out, byte);
1138
+ continue;
1139
+ }
1140
+ switch (state) {
1141
+ case SCR_TD_ISO_ASCII:
1142
+ case SCR_TD_ISO_ROMAN:
1143
+ case SCR_TD_ISO_KATAKANA:
1144
+ if (item < 0) { at_eof = true; break; }
1145
+ if (byte == 0x1b) { state = SCR_TD_ISO_ESCAPE_START; break; }
1146
+ if (state == SCR_TD_ISO_ROMAN && byte == 0x5c) {
1147
+ output_flag = false; scr_td_put(&out, 0x00a5); break;
1148
+ }
1149
+ if (state == SCR_TD_ISO_ROMAN && byte == 0x7e) {
1150
+ output_flag = false; scr_td_put(&out, 0x203e); break;
1151
+ }
1152
+ if (state == SCR_TD_ISO_KATAKANA && byte >= 0x21 && byte <= 0x5f) {
1153
+ output_flag = false; scr_td_put(&out, 0xff61 + byte - 0x21); break;
1154
+ }
1155
+ if (state != SCR_TD_ISO_KATAKANA && byte < 0x80 && byte != 0x0e && byte != 0x0f) {
1156
+ output_flag = false; scr_td_put(&out, byte); break;
1157
+ }
1158
+ output_flag = false; scr_td_error(&out); break;
1159
+
1160
+ case SCR_TD_ISO_LEAD:
1161
+ if (item < 0) { at_eof = true; break; }
1162
+ if (byte == 0x1b) { state = SCR_TD_ISO_ESCAPE_START; break; }
1163
+ if (byte >= 0x21 && byte <= 0x7e) {
1164
+ output_flag = false; lead = byte; state = SCR_TD_ISO_TRAIL; break;
1165
+ }
1166
+ /* ICU's fixed-width JIS converter folds two adjacent non-starter
1167
+ * bytes into one malformed subsequence. A valid lead, ESC, and the
1168
+ * SO/SI controls begin their own item and must remain queued; CR/LF
1169
+ * following an invalid byte is payload here rather than the line
1170
+ * reset handled above. */
1171
+ if (byte != 0x0e && byte != 0x0f && i < b->len) {
1172
+ uint8_t next = b->data[i];
1173
+ bool starts_item = next == 0x0e || next == 0x0f || next == 0x1b ||
1174
+ (next >= 0x21 && next <= 0x7e);
1175
+ if (!starts_item) i++;
1176
+ }
1177
+ output_flag = false; scr_td_error(&out); break;
1178
+
1179
+ case SCR_TD_ISO_TRAIL:
1180
+ if (item < 0) { state = SCR_TD_ISO_LEAD; scr_td_error(&out); at_eof = true; break; }
1181
+ if (byte == 0x1b) { state = SCR_TD_ISO_ESCAPE_START; scr_td_error(&out); break; }
1182
+ state = SCR_TD_ISO_LEAD;
1183
+ if (byte >= 0x21 && byte <= 0x7e) {
1184
+ uint32_t cp = scr_td_jis0208[(lead - 0x21) * 94 + byte - 0x21];
1185
+ if (cp) scr_td_put(&out, cp); else scr_td_error(&out);
1186
+ } else {
1187
+ scr_td_error(&out);
1188
+ /* SO/SI are standalone illegal items in ICU's JIS state rather
1189
+ * than trails consumed with the pending lead. Replay them so they
1190
+ * each contribute their own replacement. */
1191
+ if (byte == 0x0e || byte == 0x0f) i--;
1192
+ }
1193
+ break;
1194
+
1195
+ case SCR_TD_ISO_ESCAPE_START:
1196
+ if (item >= 0 && (byte == 0x24 || byte == 0x25 || byte == 0x26 ||
1197
+ byte == 0x28 || byte == 0x2e)) {
1198
+ lead = byte; state = SCR_TD_ISO_ESCAPE; break;
1199
+ }
1200
+ /* ICU recognizes ESC O as a complete but unsupported single-shift
1201
+ * escape, consuming the O with the replacement. */
1202
+ if (item >= 0 && byte == 0x4f) {
1203
+ output_flag = false; state = output_state; scr_td_error(&out); break;
1204
+ }
1205
+ if (item >= 0) i--;
1206
+ output_flag = false; state = output_state; scr_td_error(&out);
1207
+ if (item < 0) at_eof = true;
1208
+ break;
1209
+
1210
+ case SCR_TD_ISO_ESCAPE: {
1211
+ enum ScrTdIsoState next = (enum ScrTdIsoState)-1;
1212
+ if (item >= 0 && lead == 0x28 && byte == 0x42) next = SCR_TD_ISO_ASCII;
1213
+ else if (item >= 0 && lead == 0x28 && (byte == 0x48 || byte == 0x4a)) next = SCR_TD_ISO_ROMAN;
1214
+ else if (item >= 0 && lead == 0x28 && byte == 0x49) next = SCR_TD_ISO_KATAKANA;
1215
+ else if (item >= 0 && lead == 0x24 && (byte == 0x40 || byte == 0x42)) next = SCR_TD_ISO_LEAD;
1216
+ if ((int)next >= 0) {
1217
+ state = output_state = next;
1218
+ bool repeated = output_flag;
1219
+ output_flag = !repeated;
1220
+ if (repeated) scr_td_error(&out);
1221
+ break;
1222
+ }
1223
+ /* Some ICU designation families need one more final byte. A known
1224
+ * final consumes the whole four-byte escape as one error; an unknown
1225
+ * final restores both payload bytes and leaves that final queued. */
1226
+ bool extended_prefix =
1227
+ (lead == 0x24 && (byte == 0x28 || byte == 0x29 || byte == 0x2a || byte == 0x2b)) ||
1228
+ (lead == 0x25 && byte == 0x2f);
1229
+ if (extended_prefix) {
1230
+ if (i == b->len) {
1231
+ output_flag = false; state = output_state; scr_td_error(&out);
1232
+ break;
1233
+ }
1234
+ uint8_t final = b->data[i];
1235
+ bool known_final =
1236
+ (lead == 0x24 && byte == 0x28 &&
1237
+ ((final >= 0x40 && final <= 0x45) || (final >= 0x47 && final <= 0x4d))) ||
1238
+ (lead == 0x24 && byte == 0x29 &&
1239
+ (final == 0x41 || final == 0x43 || final == 0x45 || final == 0x47)) ||
1240
+ (lead == 0x24 && byte == 0x2a && final == 0x48) ||
1241
+ (lead == 0x24 && byte == 0x2b && final >= 0x49 && final <= 0x4d) ||
1242
+ (lead == 0x25 && byte == 0x2f &&
1243
+ ((final >= 0x40 && final <= 0x41) || (final >= 0x43 && final <= 0x46)));
1244
+ if (known_final) {
1245
+ i++;
1246
+ output_flag = false; state = output_state; scr_td_error(&out);
1247
+ break;
1248
+ }
1249
+ }
1250
+ /* ICU consumes the complete unsupported designations it recognizes,
1251
+ * while other malformed payloads are restored to the input queue. */
1252
+ bool consume_error =
1253
+ (lead == 0x24 && byte == 0x41) ||
1254
+ (lead == 0x28 && ((byte >= 0x40 && byte <= 0x47) || byte == 0x4b || byte == 0x52)) ||
1255
+ (lead == 0x25 && byte == 0x42) ||
1256
+ (lead == 0x2e && (byte == 0x41 || byte == 0x46));
1257
+ if (item >= 0 && lead == 0x26 && byte == 0x40) {
1258
+ state = output_state = SCR_TD_ISO_LEAD;
1259
+ bool repeated = output_flag;
1260
+ output_flag = !repeated;
1261
+ if (repeated) scr_td_error(&out);
1262
+ break;
1263
+ }
1264
+ if (item < 0 || consume_error) {
1265
+ output_flag = false; state = output_state; scr_td_error(&out);
1266
+ if (item < 0) at_eof = true;
1267
+ break;
1268
+ }
1269
+ i--;
1270
+ /* The first escape payload byte is restored before the current
1271
+ * byte. Replay it through the full prior state machine: in JIS
1272
+ * mode it can become the lead paired with the current byte. */
1273
+ replay = lead;
1274
+ output_flag = false; state = output_state; scr_td_error(&out);
1275
+ break;
1276
+ }
1277
+ }
1278
+ }
1279
+ return scr_td_finish(&out);
1280
+ }
1281
+
1282
+ ScrStr *scr_text_decode_legacy(const ScrBytes *b, double encoding_value) {
1283
+ unsigned encoding = (unsigned)encoding_value;
1284
+ if (encoding < SCR_TD_SINGLE_COUNT) return scr_td_single_byte(b, encoding);
1285
+ switch (encoding) {
1286
+ case SCR_TD_X_USER_DEFINED: return scr_td_x_user_defined(b);
1287
+ case SCR_TD_UTF16LE: return scr_td_utf16(b, false);
1288
+ case SCR_TD_UTF16BE: return scr_td_utf16(b, true);
1289
+ case SCR_TD_GB18030: return scr_td_gb18030_decode(b);
1290
+ case SCR_TD_BIG5: return scr_td_big5_decode(b);
1291
+ case SCR_TD_EUC_JP: return scr_td_euc_jp_decode(b);
1292
+ case SCR_TD_ISO_2022_JP: return scr_td_iso_2022_jp_decode(b);
1293
+ case SCR_TD_SHIFT_JIS: return scr_td_shift_jis_decode(b);
1294
+ case SCR_TD_EUC_KR: return scr_td_euc_kr_decode(b);
1295
+ default: return scr_str_new("", 0); /* compiler invariant */
1296
+ }
1297
+ }
1298
+ #endif /* SCR_TEXT_DECODER_LEGACY */
1299
+
736
1300
  /* Decode the next code point from an ScrStr's storage — ALWAYS valid
737
1301
  * UTF-8 (the string runtime never stores ill-formed sequences), so no
738
1302
  * validation re-runs here. */
@@ -831,6 +1395,72 @@ ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc) {
831
1395
  return scr_bytes_decode_utf8(in, n);
832
1396
  }
833
1397
 
1398
+ /* Buffer.toString with a runtime-valued encoding. Literal call sites fold
1399
+ * this same alias table in the frontend; variables arrive here, where Node
1400
+ * also accepts ASCII case variants and rejects unknown names catchably. */
1401
+ static bool scr_enc_eq_ci(const ScrStr *raw, const char *name) {
1402
+ size_t n = strlen(name);
1403
+ if (raw->len != n) return false;
1404
+ for (size_t i = 0; i < n; i++) {
1405
+ char c = raw->data[i];
1406
+ if (c >= 'A' && c <= 'Z') c = (char)(c + ('a' - 'A'));
1407
+ if (c != name[i]) return false;
1408
+ }
1409
+ return true;
1410
+ }
1411
+
1412
+ static ScrStr *scr_bytes_normalize_encoding(const ScrStr *raw) {
1413
+ static const struct { const char *from; const char *to; } map[] = {
1414
+ {"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"},
1415
+ {"base64", "base64"}, {"base64url", "base64url"},
1416
+ {"latin1", "latin1"}, {"binary", "latin1"}, {"ascii", "ascii"},
1417
+ {"utf16le", "utf16le"}, {"utf-16le", "utf16le"},
1418
+ {"ucs2", "utf16le"}, {"ucs-2", "utf16le"},
1419
+ };
1420
+ for (size_t i = 0; i < sizeof map / sizeof map[0]; i++) {
1421
+ if (scr_enc_eq_ci(raw, map[i].from)) {
1422
+ return scr_str_new(map[i].to, strlen(map[i].to));
1423
+ }
1424
+ }
1425
+ static const char prefix[] = "Unknown encoding: ";
1426
+ const size_t prefix_len = sizeof prefix - 1;
1427
+ if (raw->len > SIZE_MAX - prefix_len) scr_bytes_oom();
1428
+ const size_t msg_len = prefix_len + raw->len;
1429
+ char *msg = malloc(msg_len);
1430
+ if (!msg) scr_bytes_oom();
1431
+ memcpy(msg, prefix, prefix_len);
1432
+ memcpy(msg + prefix_len, raw->data, raw->len);
1433
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, msg_len,
1434
+ "ERR_UNKNOWN_ENCODING");
1435
+ free(msg);
1436
+ return NULL;
1437
+ }
1438
+
1439
+ ScrStr *scr_bytes_to_str_checked(const ScrBytes *b, const ScrStr *enc) {
1440
+ /* Node returns before resolving the encoding when there are no bytes
1441
+ * to decode, so even an unknown runtime name answers the empty string. */
1442
+ if (b->len == 0) return scr_str_new("", 0);
1443
+ ScrStr *normalized = scr_bytes_normalize_encoding(enc);
1444
+ if (!normalized) return NULL;
1445
+ ScrStr *out = scr_bytes_to_str(b, normalized);
1446
+ scr_str_release(normalized);
1447
+ return out;
1448
+ }
1449
+
1450
+ ScrStr *scr_bytes_to_str_checked_range(const ScrBytes *b, const ScrStr *enc,
1451
+ double start, double end) {
1452
+ /* The range is selected before Node resolves the encoding. A clamped
1453
+ * empty window therefore answers "" even for an unknown name. */
1454
+ size_t s0, e0;
1455
+ scr_bytes_to_str_bounds(b, start, end, &s0, &e0);
1456
+ if (s0 == e0) return scr_str_new("", 0);
1457
+ ScrStr *normalized = scr_bytes_normalize_encoding(enc);
1458
+ if (!normalized) return NULL;
1459
+ ScrStr *out = scr_bytes_to_str_range(b, normalized, start, end);
1460
+ scr_str_release(normalized);
1461
+ return out;
1462
+ }
1463
+
834
1464
  static int scr_hex_val(uint8_t c) {
835
1465
  if (c >= '0' && c <= '9') return c - '0';
836
1466
  if (c >= 'a' && c <= 'f') return c - 'a' + 10;
@@ -153,12 +153,14 @@ ScrBytes *scr_crypto_random_bytes(double n) {
153
153
 
154
154
  /* ── process.stdout/stderr.write(buf) ──────────────────────────────────── */
155
155
 
156
- bool scr_process_stdout_write_bytes(const ScrBytes *b) {
156
+ bool scr_process_stdout_write_bytes(const ScrBytes *b, const ScrStr *encoding) {
157
+ (void)encoding;
157
158
  scr_stdio_write(1, b->data, b->len * scr_bytes_elem_size(b->elem));
158
159
  return true;
159
160
  }
160
161
 
161
- bool scr_process_stderr_write_bytes(const ScrBytes *b) {
162
+ bool scr_process_stderr_write_bytes(const ScrBytes *b, const ScrStr *encoding) {
163
+ (void)encoding;
162
164
  scr_stdio_write(2, b->data, b->len * scr_bytes_elem_size(b->elem));
163
165
  return true;
164
166
  }