io.cpp 7.8 KB

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