/*!A cross-platform build utility based on Lua * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (C) 2015-present, Xmake Open Source Community. * * @author OpportunityLiu, ruki * @file file_read.c * */ /* ////////////////////////////////////////////////////////////////////////////////////// * trace */ #define TB_TRACE_MODULE_NAME "file_read" #define TB_TRACE_MODULE_DEBUG (0) /* ////////////////////////////////////////////////////////////////////////////////////// * includes */ #include "prefix.h" /* ////////////////////////////////////////////////////////////////////////////////////// * types */ typedef enum __xm_pushline_state_e { PL_EOF, PL_FIN, PL_CONL, PL_FAIL, } xm_pushline_state_e; /* ////////////////////////////////////////////////////////////////////////////////////// * implementation */ static tb_long_t xm_io_file_buffer_readline(tb_stream_ref_t stream, tb_buffer_ref_t line) { tb_assert_and_check_return_val(stream && line, -1); // read line and reserve crlf tb_bool_t eof = tb_false; tb_hize_t offset = 0; tb_byte_t *data = tb_null; tb_hong_t size = tb_stream_size(stream); while (size < 0 || (offset = tb_stream_offset(stream)) < size) { tb_long_t real = tb_stream_peek(stream, &data, XM_IO_BLOCK_MAXN); if (real > 0) { tb_char_t const *e = tb_strnchr((tb_char_t const *)data, real, '\n'); if (e) { tb_size_t n = (tb_byte_t const *)e + 1 - data; if (!tb_stream_skip(stream, n)) return -1; tb_buffer_memncat(line, data, n); break; } else { if (!tb_stream_skip(stream, real)) return -1; tb_buffer_memncat(line, data, real); } } else if (!real) { real = tb_stream_wait(stream, TB_STREAM_WAIT_READ, -1); if (real <= 0) { eof = tb_true; break; } } else { eof = tb_true; break; } } tb_size_t linesize = tb_buffer_size(line); if (linesize) { return linesize; } else { return (eof || tb_stream_beof(stream)) ? -1 : 0; } } static tb_int_t xm_io_file_buffer_pushline(tb_buffer_ref_t buf, xm_io_file_t *file, tb_char_t const *continuation, tb_bool_t keep_crlf) { tb_assert(buf && file && continuation && xm_io_file_is_file(file) && file->u.file_ref); // is binary? tb_bool_t is_binary = file->encoding == XM_IO_FILE_ENCODING_BINARY; if (is_binary) { continuation = ""; keep_crlf = tb_true; } // clear line buffer tb_buffer_clear(&file->rcache); // read line data tb_long_t size = xm_io_file_buffer_readline(file->u.file_ref, &file->rcache); // translate line data tb_int_t result = PL_FAIL; tb_char_t *data = tb_null; tb_size_t conlen = tb_strlen(continuation); do { // eof? if (size < 0) { result = PL_EOF; break; } // patch two '\0' tb_buffer_memncat(&file->rcache, (tb_byte_t const *)"\0\0", 2); // get line data data = (tb_char_t *)tb_buffer_data(&file->rcache); tb_assert_and_check_break(data); // no lf found if (size > 0 && data[size - 1] != '\n') { result = PL_FIN; } else if (size > 1) { // crlf? => lf if (!is_binary && data[size - 2] == '\r') { data[size - 2] = '\n'; size--; } // has continuation? tb_bool_t has_conline = conlen && size >= conlen + 1 && tb_strncmp(continuation, (tb_char_t const *)(data + size - conlen - 1), conlen) == 0; // do not keep crlf, strip the last lf if (!keep_crlf && !has_conline) { size--; } // strip it if has continuation? if (has_conline) { size -= conlen + 1; } data[size] = '\0'; result = has_conline ? PL_CONL : PL_FIN; } else { // a single '\n' if (!keep_crlf) { size = 0; } result = PL_FIN; } } while (0); // push line data if (data && size > 0 && (result == PL_FIN || result == PL_CONL)) { tb_buffer_memncat(buf, (tb_byte_t const *)data, size); } // return result return result; } static tb_int_t xm_io_file_read_all_directly(lua_State *lua, xm_io_file_t *file) { tb_assert(lua && file && xm_io_file_is_file(file) && file->u.file_ref); // init buffer tb_buffer_t buf; if (!tb_buffer_init(&buf)) { xm_io_return_error(lua, "init buffer failed!"); } tb_byte_t *data = tb_buffer_resize(&file->rcache, XM_IO_BLOCK_MAXN); if (!data) { tb_buffer_exit(&buf); xm_io_return_error(lua, "out of memory: failed to resize read cache"); } // read all tb_stream_ref_t stream = file->u.file_ref; while (!tb_stream_beof(stream)) { tb_long_t real = tb_stream_read(stream, data, XM_IO_BLOCK_MAXN); if (real > 0) { tb_buffer_memncat(&buf, data, real); } else if (!real) { real = tb_stream_wait(stream, TB_STREAM_WAIT_READ, -1); tb_check_break(real > 0); } else { break; } } if (tb_buffer_size(&buf)) { lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); } else { lua_pushliteral(lua, ""); } tb_buffer_exit(&buf); return 1; } static tb_int_t xm_io_file_read_all(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation) { tb_assert(lua && file && continuation && xm_io_file_is_file(file) && file->u.file_ref); // is binary? read all directly tb_bool_t is_binary = file->encoding == XM_IO_FILE_ENCODING_BINARY; if (is_binary) { return xm_io_file_read_all_directly(lua, file); } // init buffer tb_buffer_t buf; if (!tb_buffer_init(&buf)) { xm_io_return_error(lua, "init buffer failed!"); } // read all tb_bool_t has_content = tb_false; while (1) { switch (xm_io_file_buffer_pushline(&buf, file, continuation, tb_true)) { case PL_EOF: if (!has_content) { lua_pushliteral(lua, ""); } else { lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); } tb_buffer_exit(&buf); return 1; case PL_FIN: case PL_CONL: has_content = tb_true; continue; case PL_FAIL: default: tb_buffer_exit(&buf); xm_io_return_error(lua, "failed to read all"); break; } } } static tb_int_t xm_io_file_read_line(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation, tb_bool_t keep_crlf) { tb_assert(lua && file && continuation && xm_io_file_is_file(file) && file->u.file_ref); // init buffer tb_buffer_t buf; if (!tb_buffer_init(&buf)) { xm_io_return_error(lua, "init buffer failed!"); } // read line tb_bool_t has_content = tb_false; while (1) { switch (xm_io_file_buffer_pushline(&buf, file, continuation, keep_crlf)) { case PL_EOF: if (!has_content) { lua_pushnil(lua); } else { lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); } tb_buffer_exit(&buf); return 1; case PL_FIN: lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); tb_buffer_exit(&buf); return 1; case PL_CONL: has_content = tb_true; continue; case PL_FAIL: default: tb_buffer_exit(&buf); xm_io_return_error(lua, "failed to readline"); break; } } } static tb_int_t xm_io_file_read_n(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation, tb_long_t n) { tb_assert(lua && file && continuation && xm_io_file_is_file(file) && file->u.file_ref); // check continuation if (*continuation != '\0') { xm_io_return_error(lua, "continuation is not supported for read number of bytes"); } // check encoding if (file->encoding != XM_IO_FILE_ENCODING_BINARY) { xm_io_return_error(lua, "read number of bytes only allows binary file, reopen with 'rb' and try again"); } tb_bool_t ok = tb_false; if (n == 0) { tb_byte_t *data = tb_null; if (tb_stream_need(file->u.file_ref, &data, 1)) { lua_pushliteral(lua, ""); ok = tb_true; } } else { tb_byte_t *bufptr = tb_buffer_resize(&file->rcache, n + 1); if (bufptr) { if (tb_stream_bread(file->u.file_ref, bufptr, n)) { lua_pushlstring(lua, (tb_char_t const *)bufptr, n); ok = tb_true; } } } if (!ok) { lua_pushnil(lua); } return 1; } static tb_size_t xm_io_file_std_buffer_pushline(tb_buffer_ref_t buf, xm_io_file_t *file, tb_char_t const *continuation, tb_bool_t keep_crlf) { tb_assert(buf && file && continuation && xm_io_file_is_std(file)); // get input buffer tb_char_t strbuf[8192]; tb_size_t buflen = 0; tb_size_t result = PL_FAIL; if (tb_stdfile_gets(file->u.std_ref, strbuf, tb_arrayn(strbuf) - 1)) { buflen = tb_strlen(strbuf); } else { return PL_EOF; } tb_size_t conlen = tb_strlen(continuation); if (buflen > 0 && strbuf[buflen - 1] != '\n') { // end of file, no lf found result = PL_FIN; } else if (buflen > 1) { // crlf? => lf if (strbuf[buflen - 2] == '\r') { strbuf[buflen - 2] = '\n'; buflen--; } // has continuation? tb_bool_t has_conline = conlen && buflen >= conlen + 1 && tb_strncmp(continuation, (strbuf + buflen - conlen - 1), conlen) == 0; // do not keep crlf, strip the last lf if (!keep_crlf && !has_conline) { buflen--; } // strip it if has continuation? if (has_conline) { buflen -= conlen + 1; } strbuf[buflen] = '\0'; result = has_conline ? PL_CONL : PL_FIN; } else { // a single '\n' if (!keep_crlf) { buflen = 0; } result = PL_FIN; } if (result == PL_FIN || result == PL_CONL) { tb_buffer_memncat(buf, (tb_byte_t const *)strbuf, buflen); } return result; } static tb_int_t xm_io_file_std_read_line(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation, tb_bool_t keep_crlf) { tb_assert(lua && file && continuation && xm_io_file_is_std(file)); // init buffer tb_buffer_t buf; if (!tb_buffer_init(&buf)) { xm_io_return_error(lua, "init buffer failed!"); } // read line tb_bool_t has_content = tb_false; while (1) { switch (xm_io_file_std_buffer_pushline(&buf, file, continuation, keep_crlf)) { case PL_EOF: if (!has_content) { lua_pushnil(lua); } else { lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); } tb_buffer_exit(&buf); return 1; case PL_FIN: lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); tb_buffer_exit(&buf); return 1; case PL_CONL: has_content = tb_true; continue; case PL_FAIL: default: tb_buffer_exit(&buf); xm_io_return_error(lua, "failed to readline"); break; } } } static tb_int_t xm_io_file_std_read_all(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation) { tb_assert(lua && file && continuation && xm_io_file_is_std(file)); // init buffer tb_buffer_t buf; if (!tb_buffer_init(&buf)) xm_io_return_error(lua, "init buffer failed!"); // read all tb_bool_t has_content = tb_false; while (1) { switch (xm_io_file_std_buffer_pushline(&buf, file, continuation, tb_true)) { case PL_EOF: if (!has_content) lua_pushliteral(lua, ""); else lua_pushlstring(lua, (tb_char_t const *)tb_buffer_data(&buf), tb_buffer_size(&buf)); tb_buffer_exit(&buf); return 1; case PL_FIN: case PL_CONL: has_content = tb_true; continue; case PL_FAIL: default: tb_buffer_exit(&buf); xm_io_return_error(lua, "failed to readline"); break; } } } static tb_int_t xm_io_file_std_read_n(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation, tb_long_t n) { tb_assert(lua && file && continuation && xm_io_file_is_std(file)); // check continuation if (*continuation != '\0') xm_io_return_error(lua, "continuation is not supported for std streams"); // io.read(0) if (n == 0) { tb_char_t ch; if (!tb_stdfile_peek(file->u.std_ref, &ch)) lua_pushnil(lua); else lua_pushliteral(lua, ""); return 1; } // get line buffer tb_byte_t *buf_ptr = tb_buffer_resize(&file->rcache, (tb_size_t)n); tb_assert(buf_ptr); // io.read(n) if (tb_stdfile_read(file->u.std_ref, buf_ptr, (tb_size_t)n)) lua_pushlstring(lua, (tb_char_t const *)buf_ptr, (size_t)n); else lua_pushnil(lua); return 1; } static tb_int_t xm_io_file_std_read_num(lua_State *lua, xm_io_file_t *file, tb_char_t const *continuation) { tb_assert(lua && file && continuation && xm_io_file_is_std(file)); // check continuation if (*continuation != '\0') xm_io_return_error(lua, "continuation is not supported for std streams"); // read number tb_char_t strbuf[512]; if (tb_stdfile_gets(file->u.std_ref, strbuf, tb_arrayn(strbuf))) lua_pushnumber(lua, tb_s10tod(strbuf)); // TODO check invalid float number string and push nil else lua_pushnil(lua); return 1; } /* io.file_read(file, [mode, [continuation]]) * io.file_read(file, "all", "\\") * io.file_read(file, "L") * io.file_read(file, "l") * io.file_read(file, "n") * io.file_read(file, 10) */ tb_int_t xm_io_file_read(lua_State *lua) { tb_assert_and_check_return_val(lua, 0); // is user data? if (!lua_isuserdata(lua, 1)) xm_io_return_error(lua, "read(invalid file)!"); // get file xm_io_file_t *file = (xm_io_file_t *)lua_touserdata(lua, 1); tb_check_return_val(file, 0); // get arguments tb_char_t const *mode = luaL_optstring(lua, 2, "l"); tb_char_t const *continuation = luaL_optstring(lua, 3, ""); tb_assert_and_check_return_val(mode && continuation, 0); tb_long_t count = -1; if (lua_isnumber(lua, 2)) { count = (tb_long_t)lua_tointeger(lua, 2); if (count < 0) xm_io_return_error(lua, "invalid read size, must be positive nubmber or 0"); } else if (*mode == '*') mode++; if (xm_io_file_is_file(file)) { if (count >= 0) return xm_io_file_read_n(lua, file, continuation, count); switch (*mode) { case 'a': return xm_io_file_read_all(lua, file, continuation); case 'L': return xm_io_file_read_line(lua, file, continuation, tb_true); case 'n': xm_io_return_error(lua, "read number is not implemented"); case 'l': return xm_io_file_read_line(lua, file, continuation, tb_false); default: xm_io_return_error(lua, "unknonwn read mode"); return 0; } } else { if (count >= 0) return xm_io_file_std_read_n(lua, file, continuation, count); switch (*mode) { case 'a': return xm_io_file_std_read_all(lua, file, continuation); case 'L': return xm_io_file_std_read_line(lua, file, continuation, tb_true); case 'n': return xm_io_file_std_read_num(lua, file, continuation); case 'l': return xm_io_file_std_read_line(lua, file, continuation, tb_false); default: xm_io_return_error(lua, "unknonwn read mode"); return 0; } } }