io.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. #include "pocketpy/io.h"
  2. #if PK_ENABLE_OS
  3. #include <filesystem>
  4. #include <cstdio>
  5. #endif
  6. namespace pkpy{
  7. #if PK_ENABLE_OS
  8. struct FileIO {
  9. PY_CLASS(FileIO, io, FileIO)
  10. FILE* fp;
  11. bool is_text;
  12. FileIO(VM* vm, const Str& file, const Str& mode);
  13. void close();
  14. static void _register(VM* vm, PyObject* mod, PyObject* type);
  15. };
  16. static FILE* io_fopen(const char* name, const char* mode){
  17. #if _MSC_VER
  18. FILE* fp;
  19. errno_t err = fopen_s(&fp, name, mode);
  20. if(err != 0) return nullptr;
  21. return fp;
  22. #else
  23. return fopen(name, mode);
  24. #endif
  25. }
  26. static size_t io_fread(void* buffer, size_t size, size_t count, FILE* fp){
  27. #if _MSC_VER
  28. return fread_s(buffer, std::numeric_limits<size_t>::max(), size, count, fp);
  29. #else
  30. return fread(buffer, size, count, fp);
  31. #endif
  32. }
  33. unsigned char* _default_import_handler(const char* name_p, int name_size, int* out_size){
  34. std::string name(name_p, name_size);
  35. bool exists = std::filesystem::exists(std::filesystem::path(name));
  36. if(!exists) return nullptr;
  37. FILE* fp = io_fopen(name.c_str(), "rb");
  38. if(!fp) return nullptr;
  39. fseek(fp, 0, SEEK_END);
  40. int buffer_size = ftell(fp);
  41. unsigned char* buffer = new unsigned char[buffer_size];
  42. fseek(fp, 0, SEEK_SET);
  43. size_t sz = io_fread(buffer, 1, buffer_size, fp);
  44. (void)sz; // suppress warning
  45. fclose(fp);
  46. *out_size = buffer_size;
  47. return buffer;
  48. };
  49. void FileIO::_register(VM* vm, PyObject* mod, PyObject* type){
  50. vm->bind_constructor<3>(type, [](VM* vm, ArgsView args){
  51. Type cls = PK_OBJ_GET(Type, args[0]);
  52. return vm->heap.gcnew<FileIO>(cls, vm,
  53. py_cast<Str&>(vm, args[1]),
  54. py_cast<Str&>(vm, args[2]));
  55. });
  56. vm->bind(type, "read(self, size=-1)", [](VM* vm, ArgsView args){
  57. FileIO& io = PK_OBJ_GET(FileIO, args[0]);
  58. i64 size = CAST(i64, args[1]);
  59. i64 buffer_size;
  60. if(size < 0){
  61. long current = ftell(io.fp);
  62. fseek(io.fp, 0, SEEK_END);
  63. buffer_size = ftell(io.fp);
  64. fseek(io.fp, current, SEEK_SET);
  65. }else{
  66. buffer_size = size;
  67. }
  68. unsigned char* buffer = new unsigned char[buffer_size];
  69. i64 actual_size = io_fread(buffer, 1, buffer_size, io.fp);
  70. PK_ASSERT(actual_size <= buffer_size);
  71. // in text mode, CR may be dropped, which may cause `actual_size < buffer_size`
  72. Bytes b(buffer, actual_size);
  73. if(io.is_text) return VAR(b.str());
  74. return VAR(std::move(b));
  75. });
  76. vm->bind_method<1>(type, "write", [](VM* vm, ArgsView args){
  77. FileIO& io = PK_OBJ_GET(FileIO, args[0]);
  78. if(io.is_text){
  79. Str& s = CAST(Str&, args[1]);
  80. fwrite(s.data, 1, s.length(), io.fp);
  81. }else{
  82. Bytes& buffer = CAST(Bytes&, args[1]);
  83. fwrite(buffer.data(), 1, buffer.size(), io.fp);
  84. }
  85. return vm->None;
  86. });
  87. vm->bind_method<0>(type, "tell", [](VM* vm, ArgsView args){
  88. FileIO& io = PK_OBJ_GET(FileIO, args[0]);
  89. long pos = ftell(io.fp);
  90. if(pos == -1) vm->IOError(strerror(errno));
  91. return VAR(pos);
  92. });
  93. vm->bind_method<2>(type, "seek", [](VM* vm, ArgsView args){
  94. FileIO& io = PK_OBJ_GET(FileIO, args[0]);
  95. long offset = CAST(long, args[1]);
  96. int whence = CAST(int, args[2]);
  97. int ret = fseek(io.fp, offset, whence);
  98. if(ret != 0) vm->IOError(strerror(errno));
  99. return vm->None;
  100. });
  101. vm->bind_method<0>(type, "close", [](VM* vm, ArgsView args){
  102. FileIO& io = PK_OBJ_GET(FileIO, args[0]);
  103. io.close();
  104. return vm->None;
  105. });
  106. vm->bind_method<0>(type, "__exit__", [](VM* vm, ArgsView args){
  107. FileIO& io = PK_OBJ_GET(FileIO, args[0]);
  108. io.close();
  109. return vm->None;
  110. });
  111. vm->bind_method<0>(type, "__enter__", PK_LAMBDA(args[0]));
  112. }
  113. FileIO::FileIO(VM* vm, const Str& file, const Str& mode){
  114. this->is_text = mode.sv().find("b") == std::string::npos;
  115. fp = io_fopen(file.c_str(), mode.c_str());
  116. if(!fp) vm->IOError(strerror(errno));
  117. }
  118. void FileIO::close(){
  119. if(fp == nullptr) return;
  120. fclose(fp);
  121. fp = nullptr;
  122. }
  123. void add_module_io(VM* vm){
  124. PyObject* mod = vm->new_module("io");
  125. FileIO::register_class(vm, mod);
  126. mod->attr().set("SEEK_SET", VAR(SEEK_SET));
  127. mod->attr().set("SEEK_CUR", VAR(SEEK_CUR));
  128. mod->attr().set("SEEK_END", VAR(SEEK_END));
  129. vm->bind(vm->builtins, "open(path, mode='r')", [](VM* vm, ArgsView args){
  130. PK_LOCAL_STATIC StrName m_io("io");
  131. PK_LOCAL_STATIC StrName m_FileIO("FileIO");
  132. return vm->call(vm->_modules[m_io]->attr(m_FileIO), args[0], args[1]);
  133. });
  134. }
  135. void add_module_os(VM* vm){
  136. PyObject* mod = vm->new_module("os");
  137. PyObject* path_obj = vm->heap.gcnew<DummyInstance>(vm->tp_object);
  138. mod->attr().set("path", path_obj);
  139. // Working directory is shared by all VMs!!
  140. vm->bind_func<0>(mod, "getcwd", [](VM* vm, ArgsView args){
  141. return VAR(std::filesystem::current_path().string());
  142. });
  143. vm->bind_func<1>(mod, "chdir", [](VM* vm, ArgsView args){
  144. std::filesystem::path path(CAST(Str&, args[0]).sv());
  145. std::filesystem::current_path(path);
  146. return vm->None;
  147. });
  148. vm->bind_func<1>(mod, "listdir", [](VM* vm, ArgsView args){
  149. std::filesystem::path path(CAST(Str&, args[0]).sv());
  150. std::filesystem::directory_iterator di;
  151. try{
  152. di = std::filesystem::directory_iterator(path);
  153. }catch(std::filesystem::filesystem_error& e){
  154. std::string msg = e.what();
  155. auto pos = msg.find_last_of(":");
  156. if(pos != std::string::npos) msg = msg.substr(pos + 1);
  157. vm->IOError(Str(msg).lstrip());
  158. }
  159. List ret;
  160. for(auto& p: di) ret.push_back(VAR(p.path().filename().string()));
  161. return VAR(ret);
  162. });
  163. vm->bind_func<1>(mod, "remove", [](VM* vm, ArgsView args){
  164. std::filesystem::path path(CAST(Str&, args[0]).sv());
  165. bool ok = std::filesystem::remove(path);
  166. if(!ok) vm->IOError("operation failed");
  167. return vm->None;
  168. });
  169. vm->bind_func<1>(mod, "mkdir", [](VM* vm, ArgsView args){
  170. std::filesystem::path path(CAST(Str&, args[0]).sv());
  171. bool ok = std::filesystem::create_directory(path);
  172. if(!ok) vm->IOError("operation failed");
  173. return vm->None;
  174. });
  175. vm->bind_func<1>(mod, "rmdir", [](VM* vm, ArgsView args){
  176. std::filesystem::path path(CAST(Str&, args[0]).sv());
  177. bool ok = std::filesystem::remove(path);
  178. if(!ok) vm->IOError("operation failed");
  179. return vm->None;
  180. });
  181. vm->bind_func<-1>(path_obj, "join", [](VM* vm, ArgsView args){
  182. std::filesystem::path path;
  183. for(int i=0; i<args.size(); i++){
  184. path /= CAST(Str&, args[i]).sv();
  185. }
  186. return VAR(path.string());
  187. });
  188. vm->bind_func<1>(path_obj, "exists", [](VM* vm, ArgsView args){
  189. std::filesystem::path path(CAST(Str&, args[0]).sv());
  190. bool exists = std::filesystem::exists(path);
  191. return VAR(exists);
  192. });
  193. vm->bind_func<1>(path_obj, "basename", [](VM* vm, ArgsView args){
  194. std::filesystem::path path(CAST(Str&, args[0]).sv());
  195. return VAR(path.filename().string());
  196. });
  197. }
  198. #else
  199. void add_module_io(VM* vm){}
  200. void add_module_os(VM* vm){}
  201. unsigned char* _default_import_handler(const char* name_p, int name_size, int* out_size){
  202. return nullptr;
  203. }
  204. #endif
  205. } // namespace pkpy