@@ -1081,6 +1081,85 @@ io_print(mrb_state *mrb, mrb_value io)
10811081return mrb_nil_value();
10821082}
108310831084+/*
1085+ * call-seq:
1086+ * ios.putc(obj) -> obj
1087+ *
1088+ * If obj is Integer, write the byte (mod 256).
1089+ * If obj is String, write the first character.
1090+ * Returns obj.
1091+ */
1092+static mrb_value
1093+io_putc(mrb_state *mrb, mrb_value io)
1094+{
1095+struct mrb_io *fptr = io_get_write_fptr(mrb, io);
1096+int fd = io_get_write_fd(fptr);
1097+mrb_value c = mrb_get_arg1(mrb);
1098+const char *ptr;
1099+mrb_int write_len;
1100+1101+io_prepare_write(mrb, fptr);
1102+1103+if (mrb_integer_p(c)) {
1104+unsigned char byte = (unsigned char)(mrb_integer(c) & 0xff);
1105+ssize_t n;
1106+do {
1107+n = write(fd, &byte, 1);
1108+ } while (n == -1 && errno == EINTR);
1109+if (n == -1) mrb_sys_fail(mrb, "write");
1110+return c;
1111+ }
1112+1113+mrb_value str;
1114+if (mrb_string_p(c)) {
1115+str = c;
1116+ }
1117+else {
1118+str = mrb_obj_as_string(mrb, c);
1119+ }
1120+1121+ptr = RSTRING_PTR(str);
1122+mrb_int len = RSTRING_LEN(str);
1123+1124+if (len == 0) return c;
1125+1126+#ifdef MRB_UTF8_STRING
1127+/* Determine UTF-8 character length from first byte */
1128+unsigned char first = (unsigned char)ptr[0];
1129+if (first < 0x80) {
1130+write_len = 1; /* ASCII */
1131+ }
1132+else if ((first & 0xE0) == 0xC0) {
1133+write_len = 2; /* 2-byte UTF-8 */
1134+ }
1135+else if ((first & 0xF0) == 0xE0) {
1136+write_len = 3; /* 3-byte UTF-8 */
1137+ }
1138+else if ((first & 0xF8) == 0xF0) {
1139+write_len = 4; /* 4-byte UTF-8 */
1140+ }
1141+else {
1142+write_len = 1; /* Invalid UTF-8, write single byte */
1143+ }
1144+if (write_len > len) write_len = len;
1145+#else
1146+write_len = 1; /* Non-UTF8: write single byte */
1147+#endif
1148+1149+/* Write the character bytes */
1150+while (write_len > 0) {
1151+ssize_t n = write(fd, ptr, write_len);
1152+if (n == -1) {
1153+if (errno == EINTR) continue;
1154+mrb_sys_fail(mrb, "write");
1155+ }
1156+ptr += n;
1157+write_len -= n;
1158+ }
1159+1160+return c;
1161+}
1162+10841163/*
10851164 * call-seq:
10861165 * ios << obj -> ios
@@ -2180,6 +2259,7 @@ mrb_init_io(mrb_state *mrb)
21802259mrb_define_method_id(mrb, io, MRB_SYM(write), io_write, MRB_ARGS_ANY()); /* 15.2.20.5.20 */
21812260mrb_define_method_id(mrb, io, MRB_SYM(puts), io_puts, MRB_ARGS_ANY());
21822261mrb_define_method_id(mrb, io, MRB_SYM(print), io_print, MRB_ARGS_ANY());
2262+mrb_define_method_id(mrb, io, MRB_SYM(putc), io_putc, MRB_ARGS_REQ(1));
21832263mrb_define_method_id(mrb, io, MRB_OPSYM(lshift), io_lshift, MRB_ARGS_REQ(1));
21842264mrb_define_method_id(mrb, io, MRB_SYM(pread), io_pread, MRB_ARGS_ANY()); /* Ruby 2.5 feature */
21852265mrb_define_method_id(mrb, io, MRB_SYM(pwrite), io_pwrite, MRB_ARGS_ANY()); /* Ruby 2.5 feature */