zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 8fc2092f (21843B) - Raw


      1 const system = switch(@compileVar("os")) {
      2     Os.linux => @import("linux.zig"),
      3     Os.darwin => @import("darwin.zig"),
      4     else => @compileError("Unsupported OS"),
      5 };
      6 
      7 const errno = @import("errno.zig");
      8 const math = @import("math.zig");
      9 const debug = @import("debug.zig");
     10 const assert = debug.assert;
     11 const os = @import("os.zig");
     12 const mem = @import("mem.zig");
     13 
     14 pub const stdin_fileno = 0;
     15 pub const stdout_fileno = 1;
     16 pub const stderr_fileno = 2;
     17 
     18 pub var stdin = InStream {
     19     .fd = stdin_fileno,
     20 };
     21 
     22 pub var stdout = OutStream {
     23     .fd = stdout_fileno,
     24     .buffer = undefined,
     25     .index = 0,
     26 };
     27 
     28 pub var stderr = OutStream {
     29     .fd = stderr_fileno,
     30     .buffer = undefined,
     31     .index = 0,
     32 };
     33 
     34 /// The function received invalid input at runtime. An Invalid error means a
     35 /// bug in the program that called the function.
     36 error Invalid;
     37 
     38 /// When an Unexpected error occurs, code that emitted the error likely needs
     39 /// a patch to recognize the unexpected case so that it can handle it and emit
     40 /// a more specific error.
     41 error Unexpected;
     42 
     43 error DiskQuota;
     44 error FileTooBig;
     45 error Io;
     46 error NoSpaceLeft;
     47 error BadPerm;
     48 error PipeFail;
     49 error BadFd;
     50 error IsDir;
     51 error NotDir;
     52 error SymLinkLoop;
     53 error ProcessFdQuotaExceeded;
     54 error SystemFdQuotaExceeded;
     55 error NameTooLong;
     56 error NoDevice;
     57 error PathNotFound;
     58 error NoMem;
     59 error Unseekable;
     60 error Eof;
     61 
     62 const buffer_size = 4 * 1024;
     63 const max_f64_digits = 65;
     64 const max_int_digits = 65;
     65 
     66 pub const OpenRead     = 0b0001;
     67 pub const OpenWrite    = 0b0010;
     68 pub const OpenCreate   = 0b0100;
     69 pub const OpenTruncate = 0b1000;
     70 
     71 pub const OutStream = struct {
     72     fd: i32,
     73     buffer: [buffer_size]u8,
     74     index: usize,
     75 
     76     pub fn writeByte(self: &OutStream, b: u8) -> %void {
     77         if (self.buffer.len == self.index) %return self.flush();
     78         self.buffer[self.index] = b;
     79         self.index += 1;
     80     }
     81 
     82     pub fn write(self: &OutStream, bytes: []const u8) -> %void {
     83         var src_bytes_left = bytes.len;
     84         var src_index: usize = 0;
     85         const dest_space_left = self.buffer.len - self.index;
     86 
     87         while (src_bytes_left > 0) {
     88             const copy_amt = math.min(dest_space_left, src_bytes_left);
     89             @memcpy(&self.buffer[self.index], &bytes[src_index], copy_amt);
     90             self.index += copy_amt;
     91             if (self.index == self.buffer.len) {
     92                 %return self.flush();
     93             }
     94             src_bytes_left -= copy_amt;
     95         }
     96     }
     97 
     98     const State = enum { // TODO put inside printf function and make sure the name and debug info is correct
     99         Start,
    100         OpenBrace,
    101         CloseBrace,
    102         Integer,
    103         IntegerWidth,
    104         Character,
    105     };
    106 
    107     /// Calls print and then flushes the buffer.
    108     pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) -> %void {
    109         comptime var start_index = 0;
    110         comptime var state = State.Start;
    111         comptime var next_arg = 0;
    112         comptime var radix = 0;
    113         comptime var uppercase = false;
    114         comptime var width = 0;
    115         comptime var width_start = 0;
    116 
    117         inline for (format) |c, i| {
    118             switch (state) {
    119                 State.Start => switch (c) {
    120                     '{' => {
    121                         if (start_index < i) %return self.write(format[start_index...i]);
    122                         state = State.OpenBrace;
    123                     },
    124                     '}' => {
    125                         if (start_index < i) %return self.write(format[start_index...i]);
    126                         state = State.CloseBrace;
    127                     },
    128                     else => {},
    129                 },
    130                 State.OpenBrace => switch (c) {
    131                     '{' => {
    132                         state = State.Start;
    133                         start_index = i;
    134                     },
    135                     '}' => {
    136                         %return self.printValue(args[next_arg]);
    137                         next_arg += 1;
    138                         state = State.Start;
    139                         start_index = i + 1;
    140                     },
    141                     'd' => {
    142                         radix = 10;
    143                         uppercase = false;
    144                         width = 0;
    145                         state = State.Integer;
    146                     },
    147                     'x' => {
    148                         radix = 16;
    149                         uppercase = false;
    150                         width = 0;
    151                         state = State.Integer;
    152                     },
    153                     'X' => {
    154                         radix = 16;
    155                         uppercase = true;
    156                         width = 0;
    157                         state = State.Integer;
    158                     },
    159                     'c' => {
    160                         state = State.Character;
    161                     },
    162                     else => @compileError("Unknown format character: " ++ []u8{c}),
    163                 },
    164                 State.CloseBrace => switch (c) {
    165                     '}' => {
    166                         state = State.Start;
    167                         start_index = i;
    168                     },
    169                     else => @compileError("Single '}' encountered in format string"),
    170                 },
    171                 State.Integer => switch (c) {
    172                     '}' => {
    173                         self.printInt(args[next_arg], radix, uppercase, width);
    174                         next_arg += 1;
    175                         state = State.Start;
    176                         start_index = i + 1;
    177                     },
    178                     '0' ... '9' => {
    179                         width_start = i;
    180                         state = State.IntegerWidth;
    181                     },
    182                     else => @compileError("Unexpected character in format string: " ++ []u8{c}),
    183                 },
    184                 State.IntegerWidth => switch (c) {
    185                     '}' => {
    186                         width = comptime %%parseUnsigned(usize, format[width_start...i], 10);
    187                         self.printInt(args[next_arg], radix, uppercase, width);
    188                         next_arg += 1;
    189                         state = State.Start;
    190                         start_index = i + 1;
    191                     },
    192                     '0' ... '9' => {},
    193                     else => @compileError("Unexpected character in format string: " ++ []u8{c}),
    194                 },
    195                 State.Character => switch (c) {
    196                     '}' => {
    197                         self.printAsciiChar(args[next_arg]);
    198                         next_arg += 1;
    199                         state = State.Start;
    200                         start_index = i + 1;
    201                     },
    202                     else => @compileError("Unexpected character in format string: " ++ []u8{c}),
    203                 },
    204             }
    205         }
    206         comptime {
    207             if (args.len != next_arg) {
    208                 @compileError("Unused arguments");
    209             }
    210             if (state != State.Start) {
    211                 @compileError("Incomplete format string: " ++ format);
    212             }
    213         }
    214         if (start_index < format.len) {
    215             %return self.write(format[start_index...format.len]);
    216         }
    217         %return self.flush();
    218     }
    219 
    220     pub fn printValue(self: &OutStream, value: var) -> %void {
    221         const T = @typeOf(value);
    222         if (@isInteger(T)) {
    223             return self.printInt(value, 10, false, 0);
    224         } else if (@isFloat(T)) {
    225             return self.printFloat(T, value);
    226         } else if (@canImplicitCast([]const u8, value)) {
    227             const casted_value = ([]const u8)(value);
    228             return self.write(casted_value);
    229         } else if (T == void) {
    230             return self.write("void");
    231         } else {
    232             @compileError("Unable to print type '" ++ @typeName(T) ++ "'");
    233         }
    234     }
    235 
    236     pub fn printInt(self: &OutStream, x: var, base: u8, uppercase: bool, width: usize) -> %void {
    237         if (self.index + max_int_digits >= self.buffer.len) {
    238             %return self.flush();
    239         }
    240         const amt_printed = bufPrintInt(self.buffer[self.index...], x, base, uppercase, width);
    241         self.index += amt_printed;
    242     }
    243 
    244     pub fn printAsciiChar(self: &OutStream, c: u8) -> %void {
    245         if (self.index + 1 >= self.buffer.len) {
    246             %return self.flush();
    247         }
    248         self.buffer[self.index] = c;
    249         self.index += 1;
    250     }
    251 
    252     pub fn flush(self: &OutStream) -> %void {
    253         while (true) {
    254             const write_ret = system.write(self.fd, &self.buffer[0], self.index);
    255             const write_err = system.getErrno(write_ret);
    256             if (write_err > 0) {
    257                 return switch (write_err) {
    258                     errno.EINTR  => continue,
    259                     errno.EINVAL => @unreachable(),
    260                     errno.EDQUOT => error.DiskQuota,
    261                     errno.EFBIG  => error.FileTooBig,
    262                     errno.EIO    => error.Io,
    263                     errno.ENOSPC => error.NoSpaceLeft,
    264                     errno.EPERM  => error.BadPerm,
    265                     errno.EPIPE  => error.PipeFail,
    266                     else         => error.Unexpected,
    267                 }
    268             }
    269             self.index = 0;
    270             return;
    271         }
    272     }
    273 
    274     pub fn close(self: &OutStream) {
    275         while (true) {
    276             const close_ret = system.close(self.fd);
    277             const close_err = system.getErrno(close_ret);
    278             if (close_err > 0 && close_err == errno.EINTR)
    279                 continue;
    280             return;
    281         }
    282     }
    283 };
    284 
    285 // TODO created a BufferedInStream struct and move some of this code there
    286 // BufferedInStream API goes on top of minimal InStream API.
    287 pub const InStream = struct {
    288     fd: i32,
    289 
    290     /// Call close to clean up.
    291     pub fn open(is: &InStream, path: []const u8) -> %void {
    292         switch (@compileVar("os")) {
    293             Os.linux, Os.darwin => {
    294                 while (true) {
    295                     const result = system.open(path, system.O_LARGEFILE|system.O_RDONLY, 0);
    296                     const err = system.getErrno(result);
    297                     if (err > 0) {
    298                         return switch (err) {
    299                             errno.EINTR => continue,
    300 
    301                             errno.EFAULT => @unreachable(),
    302                             errno.EINVAL => @unreachable(),
    303                             errno.EACCES => error.BadPerm,
    304                             errno.EFBIG, errno.EOVERFLOW => error.FileTooBig,
    305                             errno.EISDIR => error.IsDir,
    306                             errno.ELOOP => error.SymLinkLoop,
    307                             errno.EMFILE => error.ProcessFdQuotaExceeded,
    308                             errno.ENAMETOOLONG => error.NameTooLong,
    309                             errno.ENFILE => error.SystemFdQuotaExceeded,
    310                             errno.ENODEV => error.NoDevice,
    311                             errno.ENOENT => error.PathNotFound,
    312                             errno.ENOMEM => error.NoMem,
    313                             errno.ENOSPC => error.NoSpaceLeft,
    314                             errno.ENOTDIR => error.NotDir,
    315                             errno.EPERM => error.BadPerm,
    316                             else => error.Unexpected,
    317                         }
    318                     }
    319                     is.fd = i32(result);
    320                     return;
    321                 }
    322             },
    323             else => @compileError("unsupported OS"),
    324         }
    325 
    326     }
    327 
    328     /// Upon success, the stream is in an uninitialized state. To continue using it,
    329     /// you must use the open() function.
    330     pub fn close(is: &InStream) -> %void {
    331         switch (@compileVar("os")) {
    332             Os.linux, Os.darwin => {
    333                 while (true) {
    334                     const close_ret = system.close(is.fd);
    335                     const close_err = system.getErrno(close_ret);
    336                     if (close_err > 0) {
    337                         return switch (close_err) {
    338                             errno.EINTR => continue,
    339 
    340                             errno.EIO => error.Io,
    341                             errno.EBADF => error.BadFd,
    342                             else => error.Unexpected,
    343                         }
    344                     }
    345                     return;
    346                 }
    347             },
    348             else => @compileError("unsupported OS"),
    349         }
    350     }
    351 
    352     /// Returns the number of bytes read. If the number read is smaller than buf.len, then
    353     /// the stream reached End Of File.
    354     pub fn read(is: &InStream, buf: []u8) -> %usize {
    355         switch (@compileVar("os")) {
    356             Os.linux, Os.darwin => {
    357                 var index: usize = 0;
    358                 while (index < buf.len) {
    359                     const amt_read = system.read(is.fd, &buf[index], buf.len - index);
    360                     const read_err = system.getErrno(amt_read);
    361                     if (read_err > 0) {
    362                         switch (read_err) {
    363                             errno.EINTR  => continue,
    364 
    365                             errno.EINVAL => @unreachable(),
    366                             errno.EFAULT => @unreachable(),
    367                             errno.EBADF  => return error.BadFd,
    368                             errno.EIO    => return error.Io,
    369                             else         => return error.Unexpected,
    370                         }
    371                     }
    372                     if (amt_read == 0) return index;
    373                     index += amt_read;
    374                 }
    375                 return index;
    376             },
    377             else => @compileError("unsupported OS"),
    378         }
    379     }
    380 
    381     pub fn readNoEof(is: &InStream, buf: []u8) -> %void {
    382         const amt_read = %return is.read(buf);
    383         if (amt_read < buf.len) return error.Eof;
    384     }
    385 
    386     pub fn readByte(is: &InStream) -> %u8 {
    387         var result: [1]u8 = undefined;
    388         %return is.readNoEof(result[0...]);
    389         return result[0];
    390     }
    391 
    392     pub fn readIntLe(is: &InStream, comptime T: type) -> %T {
    393         is.readInt(false, T)
    394     }
    395 
    396     pub fn readIntBe(is: &InStream, comptime T: type) -> %T {
    397         is.readInt(true, T)
    398     }
    399 
    400     pub fn readInt(is: &InStream, is_be: bool, comptime T: type) -> %T {
    401         var bytes: [@sizeOf(T)]u8 = undefined;
    402         %return is.readNoEof(bytes[0...]);
    403         return mem.readInt(bytes, T, is_be);
    404     }
    405 
    406     pub fn readVarInt(is: &InStream, is_be: bool, comptime T: type, size: usize) -> %T {
    407         assert(size <= @sizeOf(T));
    408         assert(size <= 8);
    409         var input_buf: [8]u8 = undefined;
    410         const input_slice = input_buf[0...size];
    411         %return is.readNoEof(input_slice);
    412         return mem.readInt(input_slice, T, is_be);
    413     }
    414 
    415     pub fn seekForward(is: &InStream, amount: usize) -> %void {
    416         switch (@compileVar("os")) {
    417             Os.linux, Os.darwin => {
    418                 const result = system.lseek(is.fd, amount, system.SEEK_CUR);
    419                 const err = system.getErrno(result);
    420                 if (err > 0) {
    421                     return switch (err) {
    422                         errno.EBADF => error.BadFd,
    423                         errno.EINVAL => error.Unseekable,
    424                         errno.EOVERFLOW => error.Unseekable,
    425                         errno.ESPIPE => error.Unseekable,
    426                         errno.ENXIO => error.Unseekable,
    427                         else => error.Unexpected,
    428                     };
    429                 }
    430             },
    431             else => @compileError("unsupported OS"),
    432         }
    433     }
    434 
    435     pub fn seekTo(is: &InStream, pos: usize) -> %void {
    436         switch (@compileVar("os")) {
    437             Os.linux, Os.darwin => {
    438                 const result = system.lseek(is.fd, pos, system.SEEK_SET);
    439                 const err = system.getErrno(result);
    440                 if (err > 0) {
    441                     return switch (err) {
    442                         errno.EBADF => error.BadFd,
    443                         errno.EINVAL => error.Unseekable,
    444                         errno.EOVERFLOW => error.Unseekable,
    445                         errno.ESPIPE => error.Unseekable,
    446                         errno.ENXIO => error.Unseekable,
    447                         else => error.Unexpected,
    448                     };
    449                 }
    450             },
    451             else => @compileError("unsupported OS"),
    452         }
    453     }
    454 
    455     pub fn getPos(is: &InStream) -> %usize {
    456         switch (@compileVar("os")) {
    457             Os.linux, Os.darwin => {
    458                 const result = system.lseek(is.fd, 0, system.SEEK_CUR);
    459                 const err = system.getErrno(result);
    460                 if (err > 0) {
    461                     return switch (err) {
    462                         errno.EBADF => error.BadFd,
    463                         errno.EINVAL => error.Unseekable,
    464                         errno.EOVERFLOW => error.Unseekable,
    465                         errno.ESPIPE => error.Unseekable,
    466                         errno.ENXIO => error.Unseekable,
    467                         else => error.Unexpected,
    468                     };
    469                 }
    470                 return result;
    471             },
    472             else => @compileError("unsupported OS"),
    473         }
    474     }
    475 
    476     pub fn getEndPos(is: &InStream) -> %usize {
    477         var stat: system.stat = undefined;
    478         const err = system.getErrno(system.fstat(is.fd, &stat));
    479         if (err > 0) {
    480             return switch (err) {
    481                 errno.EBADF => error.BadFd,
    482                 errno.ENOMEM => error.NoMem,
    483                 else => error.Unexpected,
    484             }
    485         }
    486 
    487         return usize(stat.size);
    488     }
    489 };
    490 
    491 pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) -> %T {
    492     var x: T = 0;
    493 
    494     for (buf) |c| {
    495         const digit = %return charToDigit(c, radix);
    496         x = %return math.mulOverflow(T, x, radix);
    497         x = %return math.addOverflow(T, x, digit);
    498     }
    499 
    500     return x;
    501 }
    502 
    503 error InvalidChar;
    504 fn charToDigit(c: u8, radix: u8) -> %u8 {
    505     const value = switch (c) {
    506         '0' ... '9' => c - '0',
    507         'A' ... 'Z' => c - 'A' + 10,
    508         'a' ... 'z' => c - 'a' + 10,
    509         else => return error.InvalidChar,
    510     };
    511 
    512     if (value >= radix)
    513         return error.InvalidChar;
    514 
    515     return value;
    516 }
    517 
    518 fn digitToChar(digit: u8, uppercase: bool) -> u8 {
    519     return switch (digit) {
    520         0 ... 9 => digit + '0',
    521         10 ... 35 => digit + ((if (uppercase) u8('A') else u8('a')) - 10),
    522         else => @unreachable(),
    523     };
    524 }
    525 
    526 /// Guaranteed to not use more than max_int_digits
    527 pub fn bufPrintInt(out_buf: []u8, x: var, base: u8, uppercase: bool, width: usize) -> usize {
    528     if (@typeOf(x).is_signed)
    529         bufPrintSigned(out_buf, x, base, uppercase, width)
    530     else
    531         bufPrintUnsigned(out_buf, x, base, uppercase, width)
    532 }
    533 
    534 fn bufPrintSigned(out_buf: []u8, x: var, base: u8, uppercase: bool, width: usize) -> usize {
    535     const uint = @intType(false, @typeOf(x).bit_count);
    536     if (x < 0) {
    537         out_buf[0] = '-';
    538         const new_value = uint(-(x + 1)) + 1;
    539         const new_width = if (width == 0) 0 else (width - 1);
    540         return 1 + bufPrintUnsigned(out_buf[1...], new_value, base, uppercase, new_width);
    541     } else if (width == 0) {
    542         return bufPrintUnsigned(out_buf, uint(x), base, uppercase, width);
    543     } else {
    544         out_buf[0] = '+';
    545         const new_value = uint(x);
    546         const new_width = if (width == 0) 0 else (width - 1);
    547         return 1 + bufPrintUnsigned(out_buf[1...], new_value, base, uppercase, new_width);
    548     }
    549 }
    550 
    551 fn bufPrintUnsigned(out_buf: []u8, x: var, base: u8, uppercase: bool, width: usize) -> usize {
    552     // max_int_digits accounts for the minus sign. when printing an unsigned
    553     // number we don't need to do that.
    554     var buf: [max_int_digits - 1]u8 = undefined;
    555     var a = x;
    556     var index: usize = buf.len;
    557 
    558     while (true) {
    559         const digit = a % base;
    560         index -= 1;
    561         buf[index] = digitToChar(u8(digit), uppercase);
    562         a /= base;
    563         if (a == 0)
    564             break;
    565     }
    566 
    567     const src_buf = buf[index...];
    568     const padding = if (width > src_buf.len) (width - src_buf.len) else 0;
    569 
    570     mem.set(u8, out_buf[0...padding], '0');
    571     mem.copy(u8, out_buf[padding...], src_buf);
    572     return src_buf.len + padding;
    573 }
    574 
    575 pub fn openSelfExe(stream: &InStream) -> %void {
    576     switch (@compileVar("os")) {
    577         Os.linux => {
    578             %return stream.open("/proc/self/exe");
    579         },
    580         Os.darwin => {
    581             %%stderr.printf("TODO: openSelfExe on Darwin\n");
    582             os.abort();
    583         },
    584         else => @compileError("unsupported os"),
    585     }
    586 }
    587 
    588 fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: usize) -> []u8 {
    589     return buf[0...bufPrintInt(buf, value, base, uppercase, width)];
    590 }
    591 
    592 fn testParseU64DigitTooBig() {
    593     @setFnTest(this);
    594 
    595     parseUnsigned(u64, "123a", 10) %% |err| {
    596         if (err == error.InvalidChar) return;
    597         @unreachable();
    598     };
    599     @unreachable();
    600 }
    601 
    602 fn testParseUnsignedComptime() {
    603     @setFnTest(this);
    604 
    605     comptime {
    606         assert(%%parseUnsigned(usize, "2", 10) == 2);
    607     }
    608 }
    609 
    610 fn testBufPrintInt() {
    611     @setFnTest(this);
    612 
    613     var buffer: [max_int_digits]u8 = undefined;
    614     const buf = buffer[0...];
    615     assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110"));
    616     assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, 0), "-12345678"));
    617     assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, 0), "-bc614e"));
    618     assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, 0), "-BC614E"));
    619 
    620     assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, 0), "12345678"));
    621 
    622     assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, 6), "000666"));
    623     assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 6), "001234"));
    624     assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 1), "1234"));
    625 
    626     assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, 3), "+42"));
    627     assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, 3), "-42"));
    628 }