diff --git a/binding-mruby/mrb-ext/file.cpp b/binding-mruby/mrb-ext/file.cpp new file mode 100644 index 0000000..b11adc0 --- /dev/null +++ b/binding-mruby/mrb-ext/file.cpp @@ -0,0 +1,622 @@ +/* +** file.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "file.h" + +#include "../binding-util.h" +#include "mruby/string.h" +#include "mruby/array.h" +#include "mruby/class.h" + +#include "SDL2/SDL_rwops.h" + +#include +#include +#include +#include +#include + +#include + +#include + +extern mrb_value timeFromSecondsInt(mrb_state *mrb, time_t seconds); + +static void +checkValid(mrb_state *mrb, FileImpl *p, int rwMode = FileImpl::ReadWrite) +{ + MrbData *data = getMrbData(mrb); + + if (p->closed) + mrb_raise(mrb, data->exc[IO], "closed stream"); + + if (!(p->mode & rwMode)) + { + const char *errorMsg = 0; + + switch (rwMode) + { + case FileImpl::Read : + errorMsg = "not openend for reading"; break; + case FileImpl::Write : + errorMsg = "not openend for writing"; break; + default: break; + } + + mrb_raise(mrb, data->exc[IO], errorMsg); + } +} + +extern const mrb_data_type FileType = +{ + "File", + freeInstance +}; + +static int +getOpenMode(const char *mode) +{ +#define STR_CASE(cs, ret) else if (!strcmp(mode, cs)) { return FileImpl:: ret; } + + if (!strcmp(mode, "r")) + return FileImpl::Read; + STR_CASE("rb", Read) + STR_CASE("r+", ReadWrite) + STR_CASE("w", Write) + STR_CASE("wb", Write) + STR_CASE("w+", ReadWrite) + STR_CASE("rw", ReadWrite) + STR_CASE("a", Write) + STR_CASE("a+", ReadWrite) + + qDebug() << "getOpenMode failed for:" << mode; + return 0; +} + +static void handleErrno(mrb_state *mrb) +{ + MrbException exc; + const char *msg; + mrb_value arg1; + +#define ENO(_eno, _msg) \ + case _eno: { exc = Errno##_eno; msg = _msg; break; } + + switch (errno) + { + ENO(EACCES, ""); + ENO(EBADF, ""); + ENO(EEXIST, ""); + ENO(EINVAL, ""); + ENO(EMFILE, ""); + ENO(ENOMEM, ""); + ENO(ERANGE, ""); + default: + exc = IO; msg = "Unknown Errno: %S"; + arg1 = mrb_any_to_s(mrb, mrb_fixnum_value(errno)); + } + +#undef ENO + + mrb_raisef(mrb, getMrbData(mrb)->exc[exc], msg, arg1); +} + +#define GUARD_ERRNO(stmnt) \ +{ \ + errno = 0; \ + { stmnt } \ + if (errno != 0) \ + handleErrno(mrb); \ +} + + +static char * +findLastDot(char *str) +{ + char *find = 0; + char *ptr = str; + + /* Dot in front is not considered */ + if (*ptr == '.') + ptr++; + + for (; *ptr; ++ptr) + { + /* Dot in dirpath is not considered */ + if (*ptr == '/') + { + ptr++; + if (*ptr == '.') + ptr++; + } + + if (*ptr == '.') + find = ptr; + } + + return find; +} + +/* File class methods */ +static mrb_value +fileBasename(mrb_state *mrb, mrb_value) +{ + mrb_value filename; + const char *suffix = 0; + + mrb_get_args(mrb, "S|z", &filename, &suffix); + + char *_filename = RSTRING_PTR(filename); + char *base = basename(_filename); + + char *ext = 0; + + if (suffix) + { + ext = findLastDot(base); + if (ext && !strcmp(ext, suffix)) + *ext = '\0'; + } + + mrb_value str = mrb_str_new_cstr(mrb, base); + + /* Restore param string */ + if (ext) + *ext = '.'; + + return str; +} + +static mrb_value +fileDelete(mrb_state *mrb, mrb_value) +{ + mrb_int argc; + mrb_value *argv; + + mrb_get_args(mrb, "*", &argv, &argc); + + for (int i = 0; i < argc; ++i) + { + mrb_value &v = argv[i]; + + if (!mrb_string_p(v)) + continue; + + const char *filename = RSTRING_PTR(v); + GUARD_ERRNO( unlink(filename); ) + } + + return mrb_nil_value(); +} + +static mrb_value +fileDirname(mrb_state *mrb, mrb_value) +{ + mrb_value filename; + mrb_get_args(mrb, "S", &filename); + + char *_filename = RSTRING_PTR(filename); + char *dir = dirname(_filename); + + return mrb_str_new_cstr(mrb, dir); +} + +static mrb_value +fileExpandPath(mrb_state *mrb, mrb_value) +{ + const char *path; + const char *defDir = 0; + + mrb_get_args(mrb, "z|z", &path, &defDir); + + // FIXME No idea how to integrate 'default_dir' right now + if (defDir) + qDebug() << "FIXME: File.expand_path: default_dir not implemented"; + + char buffer[512]; + char *unused = realpath(path, buffer); + (void) unused; + + return mrb_str_new_cstr(mrb, buffer); +} + +static mrb_value +fileExtname(mrb_state *mrb, mrb_value) +{ + mrb_value filename; + mrb_get_args(mrb, "S", &filename); + + char *ext = findLastDot(RSTRING_PTR(filename)); + + return mrb_str_new_cstr(mrb, ext); +} + +static mrb_value +fileOpen(mrb_state *mrb, mrb_value) +{ + mrb_value path; + const char *mode = "r"; + mrb_value block = mrb_nil_value(); + + mrb_get_args(mrb, "S|z&", &path, &mode, &block); + + /* We manually check errno because we're supplying the + * filename on ENOENT */ + errno = 0; + FILE *f = fopen(RSTRING_PTR(path), mode); + if (!f) + { + if (errno == ENOENT) + mrb_raisef(mrb, getMrbData(mrb)->exc[ErrnoENOENT], + "No such file or directory - %S", path); + else + handleErrno(mrb); + } + + SDL_RWops *ops = SDL_RWFromFP(f, SDL_TRUE); + FileImpl *p = new FileImpl(ops, getOpenMode(mode)); + + mrb_value obj = wrapObject(mrb, p, FileType); + setProperty(mrb, obj, CSpath, path); + + if (mrb_type(block) == MRB_TT_PROC) + { + // FIXME inquire how GC behaviour works here for obj + mrb_value ret = mrb_yield(mrb, block, obj); + p->close(); + return ret; + } + + return obj; +} + +static mrb_value +fileRename(mrb_state *mrb, mrb_value) +{ + const char *from, *to; + mrb_get_args(mrb, "zz", &from, &to); + + GUARD_ERRNO( rename(from, to); ) + + return mrb_fixnum_value(0); +} + + +static mrb_value +mrbNoop(mrb_state *, mrb_value self) +{ + return self; +} + +/* File instance methods */ +static mrb_value +fileClose(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p); + + GUARD_ERRNO( p->close(); ) + + return self; +} + +static void +readLine(FILE *f, QVector &buffer) +{ + buffer.clear(); + + while (true) + { + if (feof(f)) + break; + + char c = fgetc(f); + + if (c == '\n' || c == EOF) + break; + + buffer.append(c); + } +} + +static mrb_value +fileEachLine(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p, FileImpl::Read); + FILE *f = p->fp(); + + mrb_value block; + mrb_get_args(mrb, "&", &block); + + (void) f; + QVector buffer; + + mrb_value str = mrb_str_buf_new(mrb, 0); + + while (feof(f) == 0) + { + GUARD_ERRNO( readLine(f, buffer); ) + if (buffer.isEmpty() && feof(f) != 0) + break; + + size_t lineLen = buffer.count(); + mrb_str_resize(mrb, str, lineLen); + memcpy(RSTRING_PTR(str), buffer.constData(), lineLen); + + mrb_yield(mrb, block, str); + } + + return self; +} + +static mrb_value +fileEachByte(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p, FileImpl::Read); + FILE *f = p->fp(); + + mrb_value block; + mrb_get_args(mrb, "&", &block); + + GUARD_ERRNO( + while (feof(f) == 0) + { + mrb_int byte = fgetc(f); + if (byte == -1) + break; + + mrb_yield(mrb, block, mrb_fixnum_value(byte)); + }) + + return self; +} + +static mrb_value +fileIsEof(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p); + FILE *f = p->fp(); + + bool isEof; + GUARD_ERRNO( isEof = feof(f) != 0; ) + + return mrb_bool_value(isEof); +} + +static mrb_value +fileSetPos(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p); + FILE *f = p->fp(); + + mrb_int pos; + mrb_get_args(mrb, "i", &pos); + + GUARD_ERRNO( fseek(f, pos, SEEK_SET); ) + + return self; +} + +static mrb_value +fileGetPos(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p); + FILE *f = p->fp(); + + long pos; + GUARD_ERRNO( pos = ftell(f); ) + + return mrb_fixnum_value(pos); +} + +static mrb_value +fileRead(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p, FileImpl::Read); + FILE *f = p->fp(); + + long cur, size; + GUARD_ERRNO( cur = ftell(f); ) + GUARD_ERRNO( fseek(f, 0, SEEK_END); ) + GUARD_ERRNO( size = ftell(f); ) + GUARD_ERRNO( fseek(f, cur, SEEK_SET); ) + + mrb_int length = size - cur; + mrb_get_args(mrb, "|i", &length); + + mrb_value str = mrb_str_new(mrb, 0, 0); + mrb_str_resize(mrb, str, length); + + GUARD_ERRNO( int unused = fread(RSTRING_PTR(str), 1, length, f); (void) unused; ) + + return str; +} + +// FIXME this function always splits on newline right now, +// to make rs fully work, I'll have to use some strrstr magic I guess +static mrb_value +fileReadLines(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p, FileImpl::Read); + FILE *f = p->fp(); + + mrb_value arg; + mrb_get_args(mrb, "|o", &arg); + + const char *rs = "\n"; (void) rs; + if (mrb->c->ci->argc > 0) + { + qDebug() << "FIXME: File.readlines: rs not implemented"; + + if (mrb_string_p(arg)) + rs = RSTRING_PTR(arg); + else if (mrb_nil_p(arg)) + rs = "\n"; + else + mrb_raise(mrb, getMrbData(mrb)->exc[TypeError], "Expected string or nil"); // FIXME add "got <> instead" remark + } + + mrb_value ary = mrb_ary_new(mrb); + QVector buffer; + + while (feof(f) == 0) + { + GUARD_ERRNO( readLine(f, buffer); ) + if (buffer.isEmpty() && feof(f) != 0) + break; + + mrb_value str = mrb_str_new(mrb, buffer.constData(), buffer.size()); + mrb_ary_push(mrb, ary, str); + } + + return ary; +} + +static mrb_value +fileWrite(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p, FileImpl::Write); + FILE *f = p->fp(); + + mrb_value str; + mrb_get_args(mrb, "S", &str); + + size_t count; + GUARD_ERRNO( count = fwrite(RSTRING_PTR(str), 1, RSTRING_LEN(str), f); ) + + return mrb_fixnum_value(count); +} + +static mrb_value +fileGetPath(mrb_state *mrb, mrb_value self) +{ + FileImpl *p = getPrivateData(mrb, self); + checkValid(mrb, p); + + return getProperty(mrb, self, CSpath); +} + +static void +getFileStat(mrb_state *mrb, struct stat &fileStat) +{ + const char *filename; + mrb_get_args(mrb, "z", &filename); + + stat(filename, &fileStat); +} + +static mrb_value +fileGetMtime(mrb_state *mrb, mrb_value self) +{ + mrb_value path = getProperty(mrb, self, CSpath); + + struct stat fileStat; + stat(RSTRING_PTR(path), &fileStat); + + return timeFromSecondsInt(mrb, fileStat.st_mtim.tv_sec); +} + +/* FileTest module functions */ +static mrb_value +fileTestDoesExist(mrb_state *mrb, mrb_value) +{ + const char *filename; + mrb_get_args(mrb, "z", &filename); + + int result = access(filename, F_OK); + + return mrb_bool_value(result == 0); +} + +static mrb_value +fileTestIsFile(mrb_state *mrb, mrb_value) +{ + struct stat fileStat; + getFileStat(mrb, fileStat); + + return mrb_bool_value(S_ISREG(fileStat.st_mode)); +} + +static mrb_value +fileTestIsDirectory(mrb_state *mrb, mrb_value) +{ + struct stat fileStat; + getFileStat(mrb, fileStat); + + return mrb_bool_value(S_ISDIR(fileStat.st_mode)); +} + +static mrb_value +fileTestSize(mrb_state *mrb, mrb_value) +{ + struct stat fileStat; + getFileStat(mrb, fileStat); + + return mrb_fixnum_value(fileStat.st_size); +} + +void +fileBindingInit(mrb_state *mrb) +{ + mrb_define_method(mrb, mrb->kernel_module, "open", fileOpen, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK()); + + RClass *klass = mrb_define_class(mrb, "IO", 0); + klass = mrb_define_class(mrb, "File", klass); + + mrb_define_class_method(mrb, klass, "basename", fileBasename, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + mrb_define_class_method(mrb, klass, "delete", fileDelete, MRB_ARGS_REQ(1) | MRB_ARGS_ANY()); + mrb_define_class_method(mrb, klass, "dirname", fileDirname, MRB_ARGS_REQ(1)); + mrb_define_class_method(mrb, klass, "expand_path", fileExpandPath, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + mrb_define_class_method(mrb, klass, "extname", fileExtname, MRB_ARGS_REQ(1)); + mrb_define_class_method(mrb, klass, "open", fileOpen, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK()); + mrb_define_class_method(mrb, klass, "rename", fileRename, MRB_ARGS_REQ(2)); + + /* IO */ + mrb_define_method(mrb, klass, "binmode", mrbNoop, MRB_ARGS_NONE()); + mrb_define_method(mrb, klass, "close", fileClose, MRB_ARGS_NONE()); + mrb_define_method(mrb, klass, "each_line", fileEachLine, MRB_ARGS_BLOCK()); + mrb_define_method(mrb, klass, "each_byte", fileEachByte, MRB_ARGS_BLOCK()); + mrb_define_method(mrb, klass, "eof?", fileIsEof, MRB_ARGS_NONE()); + mrb_define_method(mrb, klass, "pos", fileGetPos, MRB_ARGS_NONE()); + mrb_define_method(mrb, klass, "pos=", fileSetPos, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, klass, "read", fileRead, MRB_ARGS_OPT(1)); + mrb_define_method(mrb, klass, "readlines", fileReadLines, MRB_ARGS_OPT(1)); + mrb_define_method(mrb, klass, "write", fileWrite, MRB_ARGS_REQ(1)); + + /* File */ + mrb_define_method(mrb, klass, "mtime", fileGetMtime, MRB_ARGS_NONE()); + mrb_define_method(mrb, klass, "path", fileGetPath, MRB_ARGS_NONE()); + + /* FileTest */ + RClass *module = mrb_define_module(mrb, "FileTest"); + mrb_define_module_function(mrb, module, "exist?", fileTestDoesExist, MRB_ARGS_REQ(1)); + mrb_define_module_function(mrb, module, "directory?", fileTestIsDirectory, MRB_ARGS_REQ(1)); + mrb_define_module_function(mrb, module, "file?", fileTestIsFile, MRB_ARGS_REQ(1)); + mrb_define_module_function(mrb, module, "size", fileTestSize, MRB_ARGS_REQ(1)); +} diff --git a/binding-mruby/mrb-ext/file.h b/binding-mruby/mrb-ext/file.h new file mode 100644 index 0000000..5d35277 --- /dev/null +++ b/binding-mruby/mrb-ext/file.h @@ -0,0 +1,71 @@ +/* +** file.h +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#ifndef BINDING_FILE_H +#define BINDING_FILE_H + +#include "../binding-util.h" + +#include "SDL2/SDL_rwops.h" + +struct FileImpl +{ + SDL_RWops *ops; + bool closed; + + enum OpenMode + { + Read = 1 << 0, + Write = 1 << 1, + ReadWrite = Read | Write + }; + + int mode; + + FileImpl(SDL_RWops *ops, int mode) + : ops(ops), + closed(false), + mode(mode) + {} + + void close() + { + if (closed) + return; + + SDL_RWclose(ops); + closed = true; + } + + FILE *fp() + { + return ops->hidden.stdio.fp; + } + + ~FileImpl() + { + close(); + } +}; + +DECL_TYPE(File); + +#endif // BINDING_FILE_H diff --git a/binding-mruby/mrb-ext/kernel.cpp b/binding-mruby/mrb-ext/kernel.cpp new file mode 100644 index 0000000..5f20b3c --- /dev/null +++ b/binding-mruby/mrb-ext/kernel.cpp @@ -0,0 +1,243 @@ +/* +** kernel.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "mruby.h" +#include "mruby/string.h" +#include "mruby/compile.h" + +#include +#include + +#include "SDL2/SDL_messagebox.h" + +#include "../binding-util.h" +#include "marshal.h" +#include "globalstate.h" +#include "eventthread.h" +#include "exception.h" +#include "filesystem.h" + +#include + +void mrbBindingTerminate(); + +static mrb_value +kernelEval(mrb_state *mrb, mrb_value) +{ + const char *exp; + mrb_int expLen; + mrb_get_args(mrb, "s", &exp, &expLen); + + return mrb_load_nstring(mrb, exp, expLen); +} + +static void printP(mrb_state *mrb, + const char *conv, const char *sep) +{ + mrb_int argc; + mrb_value *argv; + mrb_get_args(mrb, "*", &argv, &argc); + + mrb_value buffer = mrb_str_buf_new(mrb, 128); + mrb_value arg; + + for (int i = 0; i < argc; ++i) + { + arg = argv[i]; + arg = mrb_funcall(mrb, arg, conv, 0); + + mrb_str_buf_append(mrb, buffer, arg); + + if (i < argc) + mrb_str_buf_cat(mrb, buffer, sep, strlen(sep)); + } + + gState->eThread().showMessageBox(RSTRING_PTR(buffer), SDL_MESSAGEBOX_INFORMATION); +} + +static mrb_value +kernelP(mrb_state *mrb, mrb_value) +{ + printP(mrb, "inspect", "\n"); + + return mrb_nil_value(); +} + +static mrb_value +kernelPrint(mrb_state *mrb, mrb_value) +{ + printP(mrb, "to_s", ""); + + return mrb_nil_value(); +} + +// FIXME store this in kernel module +static int currentSeed = 1; +bool srandCalled = false; + +static void +srandCurrentTime(int *currentSeed) +{ + timeval tv; + gettimeofday(&tv, 0); + + // FIXME could check how MRI does this + *currentSeed = tv.tv_sec * tv.tv_usec; + + srand(*currentSeed); +} + +static mrb_value +kernelRand(mrb_state *mrb, mrb_value) +{ + if (!srandCalled) + { + srandCurrentTime(¤tSeed); + srandCalled = true; + } + + mrb_value arg; + + mrb_get_args(mrb, "o", &arg); + + if (mrb_fixnum_p(arg) && mrb_fixnum(arg) != 0) + { + return mrb_fixnum_value(rand() % mrb_fixnum(arg)); + } + else if ((mrb_fixnum_p(arg) && mrb_fixnum(arg) == 0) || mrb_nil_p(arg)) + { + return mrb_float_value(mrb, (float) rand() / RAND_MAX); + } + else if (mrb_float_p(arg)) + { + return mrb_float_value(mrb, (float) rand() / RAND_MAX); + } + else + { + mrb_raisef(mrb, E_TYPE_ERROR, "Expected Fixnum or Float, got %S", + mrb_str_new_cstr(mrb, mrb_class_name(mrb, mrb_class(mrb, arg)))); + return mrb_nil_value(); + } +} + +static mrb_value +kernelSrand(mrb_state *mrb, mrb_value) +{ + srandCalled = true; + + if (mrb->c->ci->argc == 1) + { + mrb_int seed; + mrb_get_args(mrb, "i", &seed); + srand(seed); + + int oldSeed = currentSeed; + currentSeed = seed; + + return mrb_fixnum_value(oldSeed); + } + else + { + int oldSeed = currentSeed; + srandCurrentTime(¤tSeed); + + return mrb_fixnum_value(oldSeed); + } +} + +static mrb_value +kernelExit(mrb_state *, mrb_value) +{ + mrbBindingTerminate(); + + return mrb_nil_value(); +} + +static mrb_value +kernelLoadData(mrb_state *mrb, mrb_value) +{ + const char *filename; + mrb_get_args(mrb, "z", &filename); + + SDL_RWops ops; + GUARD_EXC( gState->fileSystem().openRead(ops, filename); ) + + mrb_value obj; + try { obj = marshalLoadInt(mrb, &ops); } + catch (const Exception &e) + { + SDL_RWclose(&ops); + raiseMrbExc(mrb, e); + } + + SDL_RWclose(&ops); + + return obj; +} + +static mrb_value +kernelSaveData(mrb_state *mrb, mrb_value) +{ + mrb_value obj; + const char *filename; + + mrb_get_args(mrb, "oz", &obj, &filename); + + SDL_RWops *ops = SDL_RWFromFile(filename, "w"); + if (!ops) + mrb_raise(mrb, getMrbData(mrb)->exc[SDL], SDL_GetError()); + + try { marshalDumpInt(mrb, ops, obj); } + catch (const Exception &e) + { + SDL_RWclose(ops); + raiseMrbExc(mrb, e); + } + + SDL_RWclose(ops); + + return mrb_nil_value(); +} + +static mrb_value +kernelInteger(mrb_state *mrb, mrb_value) +{ + mrb_value obj; + mrb_get_args(mrb, "o", &obj); + + return mrb_to_int(mrb, obj); +} + +void kernelBindingInit(mrb_state *mrb) +{ + RClass *module = mrb->kernel_module; + + mrb_define_module_function(mrb, module, "eval", kernelEval, MRB_ARGS_REQ(1)); + mrb_define_module_function(mrb, module, "p", kernelP, MRB_ARGS_REQ(1) | MRB_ARGS_REST()); + mrb_define_module_function(mrb, module, "print", kernelPrint, MRB_ARGS_REQ(1) | MRB_ARGS_REST()); + mrb_define_module_function(mrb, module, "rand", kernelRand, MRB_ARGS_REQ(1)); + mrb_define_module_function(mrb, module, "srand", kernelSrand, MRB_ARGS_OPT(1)); + mrb_define_module_function(mrb, module, "exit", kernelExit, MRB_ARGS_NONE()); + mrb_define_module_function(mrb, module, "load_data", kernelLoadData, MRB_ARGS_REQ(1)); + mrb_define_module_function(mrb, module, "save_data", kernelSaveData, MRB_ARGS_REQ(2)); + mrb_define_module_function(mrb, module, "exit", kernelExit, MRB_ARGS_NONE()); + mrb_define_module_function(mrb, module, "Integer", kernelInteger, MRB_ARGS_REQ(1)); +} diff --git a/binding-mruby/mrb-ext/marshal.cpp b/binding-mruby/mrb-ext/marshal.cpp new file mode 100644 index 0000000..b32080a --- /dev/null +++ b/binding-mruby/mrb-ext/marshal.cpp @@ -0,0 +1,1032 @@ +/* +** marshal.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "marshal.h" + +#include "mruby/array.h" +#include "mruby/hash.h" +#include "mruby/khash.h" +#include "mruby/string.h" +#include "mruby/variable.h" +#include "mruby/class.h" + +#include +#include +#include +#include + +#include "../binding-util.h" +#include "file.h" +#include "rwmem.h" +#include "exception.h" + +#include "SDL2/SDL_timer.h" + +#include +#include +#include + +#include + + +#define MARSHAL_MAJOR 4 +#define MARSHAL_MINOR 8 + +#define TYPE_NIL '0' +#define TYPE_TRUE 'T' +#define TYPE_FALSE 'F' +#define TYPE_FIXNUM 'i' + +#define TYPE_OBJECT 'o' +//#define TYPE_DATA 'd' +#define TYPE_USERDEF 'u' +#define TYPE_USRMARSHAL 'U' +#define TYPE_FLOAT 'f' +//#define TYPE_BIGNUM 'l' +#define TYPE_STRING '"' +//#define TYPE_REGEXP '/' +#define TYPE_ARRAY '[' +#define TYPE_HASH '{' +#define TYPE_HASH_DEF '}' +#define TYPE_MODULE_OLD 'M' +#define TYPE_IVAR 'I' +#define TYPE_LINK '@' + +#define TYPE_SYMBOL ':' +#define TYPE_SYMLINK ';' + +// FIXME make these dynamically allocatd, per MarshalContext +static char gpbuffer[512]; + +inline uint qHash(mrb_value key) +{ + return qHash(key.value.p); +} + +inline bool operator==(mrb_value v1, mrb_value v2) +{ + if (mrb_type(v1) != mrb_type(v2)) + return false; + + switch (mrb_type(v1)) + { + case MRB_TT_TRUE: + return true; + + case MRB_TT_FALSE: + case MRB_TT_FIXNUM: + return (v1.value.i == v2.value.i); + case MRB_TT_SYMBOL: + return (v1.value.sym == v2.value.sym); + + case MRB_TT_FLOAT: + return (mrb_float(v1) == mrb_float(v2)); + + default: + return (mrb_ptr(v1) == mrb_ptr(v2)); + } +} + +template +struct LinkBuffer +{ + /* Key: val, Value: idx in vec */ + QHash hash; + QVector vec; + + bool contains(T value) + { + return hash.contains(value); + } + + int add(T value) + { + int idx = vec.count(); + + hash.insert(value, idx); + vec.append(value); + + return idx; + } + + T lookup(int idx) + { + return vec[idx]; + } + + int lookupIdx(T value) + { + return hash[value]; + } +}; + +struct MarshalContext +{ + SDL_RWops *ops; + mrb_state *mrb; + mrb_int limit; + + /* For Symlinks/Links */ + LinkBuffer symbols; + LinkBuffer objects; + + char readByte() + { + char byte; + int result = SDL_RWread(ops, &byte, 1, 1); + + if (result < 1) + throw Exception(Exception::ArgumentError, "dump format error"); + + return byte; + } + + void readData(char *dest, int len) + { + int result = SDL_RWread(ops, dest, 1, len); + + if (result < len) + throw Exception(Exception::ArgumentError, "dump format error"); + } + + void writeByte(char byte) + { + int result = SDL_RWwrite(ops, &byte, 1, 1); + + if (result < 1) // FIXME not sure what the correct error would be here + throw Exception(Exception::IOError, "dump writing error"); + } + + void writeData(const char *data, int len) + { + int result = SDL_RWwrite(ops, data, 1, len); + + if (result < len) // FIXME not sure what the correct error would be here + throw Exception(Exception::IOError, "dump writing error"); + } +}; + + +static int +read_fixnum(MarshalContext *ctx) +{ + char head = ctx->readByte(); + + if (head == 0) + return 0; + else if (head > 5) + return head - 5; + else if (head < -4) + return head + 5; + + int pos = (head > 0); + int len = pos ? head : head * -1; + + char n1, n2, n3, n4; + + if (pos) + n2 = n3 = n4 = 0; + else + n2 = n3 = n4 = 0xFF; + + n1 = ctx->readByte(); + if (len >= 2) + n2 = ctx->readByte(); + if (len >= 3) + n3 = ctx->readByte(); + if (len >= 4) + n4 = ctx->readByte(); + + int result = ((0xFF << 0x00) & (n1 << 0x00)) + | ((0xFF << 0x08) & (n2 << 0x08)) + | ((0xFF << 0x10) & (n3 << 0x10)) + | ((0xFF << 0x18) & (n4 << 0x18)); + +// if (!pos) +// result = -((result ^ 0xFFFFFFFF) + 1); + + return result; +} + +static float +read_float(MarshalContext *ctx) +{ + int len = read_fixnum(ctx); + + ctx->readData(gpbuffer, len); + gpbuffer[len] = '\0'; + + return strtof(gpbuffer, 0); +} + +static char * +read_string(MarshalContext *ctx) +{ + int len = read_fixnum(ctx); + + ctx->readData(gpbuffer, len); + gpbuffer[len] = '\0'; + + return gpbuffer; +} + +static mrb_value +read_string_value(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + int len = read_fixnum(ctx); + + struct RString *str = + (struct RString*) mrb_obj_alloc + (mrb, MRB_TT_STRING, mrb->string_class); + + str->c = mrb->string_class; + str->len = len; + str->aux.capa = len; + str->ptr = (char*) mrb_malloc(mrb, len+1); + + ctx->readData(str->ptr, len); + str->ptr[len] = '\0'; + + mrb_value str_obj = mrb_obj_value(str); + + return str_obj; +} + +static mrb_value read_value(MarshalContext *ctx); + +static mrb_value +read_array(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + int len = read_fixnum(ctx); + int i; + + mrb_value array = mrb_ary_new_capa(mrb, len); + + ctx->objects.add(array); + + for (i = 0; i < len; ++i) + { + mrb_value val = read_value(ctx); + mrb_ary_set(mrb, array, i, val); + } + + return array; +} + +static mrb_value +read_hash(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + int len = read_fixnum(ctx); + int i; + + mrb_value hash = mrb_hash_new_capa(mrb, len); + + ctx->objects.add(hash); + + for (i = 0; i < len; ++i) + { + mrb_value key = read_value(ctx); + mrb_value val = read_value(ctx); + + mrb_hash_set(mrb, hash, key, val); + } + + return hash; +} + +static mrb_sym +read_symbol(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + + mrb_sym symbol = mrb_intern(mrb, read_string(ctx)); + ctx->symbols.add(symbol); + + return symbol; +} + +static mrb_sym +read_symlink(MarshalContext *ctx) +{ + int idx = read_fixnum(ctx); + + if (idx > ctx->symbols.vec.size()-1) + throw Exception(Exception::ArgumentError, "bad symlink"); + + return ctx->symbols.lookup(idx); +} + +static mrb_value +read_link(MarshalContext *ctx) +{ + int idx = read_fixnum(ctx); + + if (idx > ctx->objects.vec.size()-1) + throw Exception(Exception::ArgumentError, "dump format error (unlinked)"); + + return ctx->objects.lookup(idx); +} + +mrb_value +read_instance_var(MarshalContext *ctx) +{ + mrb_value obj = read_value(ctx); + + int iv_count = read_fixnum(ctx); + int i; + + ctx->objects.add(obj); + + for (i = 0; i < iv_count; ++i) + { + mrb_value iv_name = read_value(ctx); + mrb_value iv_value = read_value(ctx); + (void)iv_name; + (void)iv_value; + + // Instance vars for String/Array not supported yet +// mrb_iv_set(mrb, obj, mrb_symbol(iv_name), iv_value); + } + + return obj; +} + +static struct RClass * +mrb_class_from_path(mrb_state *mrb, mrb_value path) +{ + char *path_s; + switch (path.tt) + { + case MRB_TT_SYMBOL : + path_s = (char*) mrb_sym2name(mrb, mrb_symbol(path)); + break; + case MRB_TT_STRING : + path_s = (char*) RSTRING_PTR(path); + break; + default: + throw Exception(Exception::ArgumentError, "dump format error for symbol"); + } + + /* If a symbol contains any colons, mrb_sym2name + * will return the string with "s around it + * (e.g. :Mod::Class => :"Mod::Class"), + * so we need to skip those. */ + if (path_s[0] == '\"') + path_s++; + + char *p, *pbgn; + mrb_value klass = mrb_obj_value(mrb->object_class); + + p = pbgn = path_s; + + while (*p && *p != '\"') + { + while (*p && *p != ':' && *p != '\"') p++; + + mrb_sym sym = mrb_intern2(mrb, pbgn, p-pbgn); + klass = mrb_const_get(mrb, klass, sym); + + if (p[0] == ':') + { + if (p[1] != ':') + return 0; + p += 2; + pbgn = p; + } + } + + return (struct RClass*) mrb_obj_ptr(klass); +} + +static mrb_value +read_object(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + mrb_value class_path = read_value(ctx); + + struct RClass *klass = + mrb_class_from_path(mrb, class_path); + + mrb_value obj = mrb_obj_value(mrb_obj_alloc(mrb, MRB_TT_OBJECT, klass)); + + ctx->objects.add(obj); + + int iv_count = read_fixnum(ctx); + int i; + + for (i = 0; i < iv_count; ++i) + { + mrb_value iv_name = read_value(ctx); + mrb_value iv_value = read_value(ctx); + + mrb_obj_iv_set(mrb, mrb_obj_ptr(obj), + mrb_symbol(iv_name), iv_value); + } + + return obj; +} + +static mrb_value +read_userdef(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + mrb_value class_path = read_value(ctx); + + struct RClass *klass = + mrb_class_from_path(mrb, class_path); + + /* Should check here if klass implements '_load()' */ + if (!mrb_obj_respond_to(mrb_class(mrb, mrb_obj_value(klass)), mrb_intern_cstr(mrb, "_load"))) + throw Exception(Exception::TypeError, + "class %s needs to have method '_load'", + RSTRING_PTR(class_path)); + + mrb_value data = read_string_value(ctx); + mrb_value obj = mrb_funcall(mrb, mrb_obj_value(klass), "_load", 1, data); + + return obj; +} + +static int maxArena = 0; + +static mrb_value +read_value(MarshalContext *ctx) +{ + mrb_state *mrb = ctx->mrb; + char type = ctx->readByte(); + mrb_value value; + if (mrb->arena_idx > maxArena) + maxArena = mrb->arena_idx; + + int arena = mrb_gc_arena_save(mrb); + + switch (type) + { + case TYPE_NIL : + value = mrb_nil_value(); + break; + + case TYPE_TRUE : + value = mrb_true_value(); + break; + + case TYPE_FALSE : + value = mrb_false_value(); + break; + + case TYPE_FIXNUM : + value = mrb_fixnum_value(read_fixnum(ctx)); + break; + + case TYPE_FLOAT : + value = mrb_float_value(mrb, read_float(ctx)); + ctx->objects.add(value); + break; + + case TYPE_STRING : + value = read_string_value(ctx); + ctx->objects.add(value); + break; + + case TYPE_ARRAY : + value = read_array(ctx); + break; + + case TYPE_HASH : + value = read_hash(ctx); + break; + + case TYPE_SYMBOL : + value = mrb_symbol_value(read_symbol(ctx)); + break; + + case TYPE_SYMLINK : + value = mrb_symbol_value(read_symlink(ctx)); + break; + + case TYPE_OBJECT : + value = read_object(ctx); + break; + + case TYPE_IVAR : + value = read_instance_var(ctx); + break; + + case TYPE_LINK : + value = read_link(ctx); + break; + + case TYPE_USERDEF : + value = read_userdef(ctx); + ctx->objects.add(value); + break; + + default : + qDebug() << "Value type" << (char)type << "not supported!"; + throw Exception(Exception::MKXPError, "Marshal.load: unsupported value type '%s'", + QByteArray(1, (char)type)); + } + + mrb_gc_arena_restore(mrb, arena); + + return value; +} + +static void +write_fixnum(MarshalContext *ctx, int value) +{ + if (value == 0) + { + ctx->writeByte(0); + return; + } + else if (value > 0 && value < 123) + { + ctx->writeByte((char) value + 5); + return; + } + else if (value < 0 && value > -124) + { + ctx->writeByte((char) value - 5); + return; + } + + char len; + + if (value > 0) + { + /* Positive number */ + if (value <= 0x7F) + { + /* 1 byte wide */ + len = 1; + } + else if (value <= 0x7FFF) + { + /* 2 bytes wide */ + len = 2; + } + else if (value <= 0x7FFFFF) + { + /* 3 bytes wide */ + len = 3; + } + else + { + /* 4 bytes wide */ + len = 4; + } + } + else + { + /* Negative number */ + if (value >= (int)0x80) + { + /* 1 byte wide */ + len = -1; + } + else if (value >= (int)0x8000) + { + /* 2 bytes wide */ + len = -2; + } + else if (value <= (int)0x800000) + { + /* 3 bytes wide */ + len = -3; + } + else + { + /* 4 bytes wide */ + len = -4; + } + } + + /* Write length */ + ctx->writeByte(len); + + /* Write bytes */ + if (len >= 1 || len <= -1) + { + ctx->writeByte((value & 0x000000FF) >> 0x00); + } + if (len >= 2 || len <= -2) + { + ctx->writeByte((value & 0x0000FF00) >> 0x08); + } + if (len >= 3 || len <= -3) + { + ctx->writeByte((value & 0x00FF0000) >> 0x10); + } + if (len == 4 || len == -4) + { + ctx->writeByte((value & 0xFF000000) >> 0x18); + } +} + +static void +write_float(MarshalContext *ctx, float value) +{ + sprintf(gpbuffer, "%.16g", value); + + int len = strlen(gpbuffer); + write_fixnum(ctx, len); + ctx->writeData(gpbuffer, len); +} + +static void +write_string(MarshalContext *ctx, const char *string) +{ + int len = strlen(string); + + write_fixnum(ctx, len); + ctx->writeData(string, len); +} + +static void +write_string_value(MarshalContext *ctx, mrb_value string) +{ + int len = RSTRING_LEN(string); + const char *ptr = RSTRING_PTR(string); + + write_fixnum(ctx, len); + ctx->writeData(ptr, len); +} + +static void write_value(MarshalContext *, mrb_value); + +static void +write_array(MarshalContext *ctx, mrb_value array) +{ + mrb_state *mrb = ctx->mrb; + int len = mrb_ary_len(mrb, array); + write_fixnum(ctx, len); + + int i; + for (i = 0; i < len; ++i) + { + mrb_value value = mrb_ary_entry(array, i); + write_value(ctx, value); + } +} + +KHASH_DECLARE(ht, mrb_value, mrb_value, 1) + +static void +write_hash(MarshalContext *ctx, mrb_value hash) +{ + mrb_state *mrb = ctx->mrb; + int len = 0; + + kh_ht *h = mrb_hash_tbl(mrb, hash); + if (h) + len = kh_size(h); + + write_fixnum(ctx, len); + + for (khiter_t k = kh_begin(h); k != kh_end(h); ++k) + { + if (!kh_exist(h, k)) + continue; + + write_value(ctx, kh_key(h, k)); + write_value(ctx, kh_val(h, k)); + } +} + +static void +write_symbol(MarshalContext *ctx, mrb_value symbol) +{ + mrb_state *mrb = ctx->mrb; + mrb_sym sym = mrb_symbol(symbol); + size_t len; + const char *p = mrb_sym2name_len(mrb, sym, &len); + + write_string(ctx, p); +} + +static void +write_object(MarshalContext *ctx, mrb_value object) +{ + mrb_state *mrb = ctx->mrb; + struct RObject *o = mrb_obj_ptr(object); + + mrb_value path = mrb_class_path(mrb, o->c); + write_value(ctx, mrb_str_intern(mrb, path)); + + mrb_value iv_ary = mrb_obj_instance_variables(mrb, object); + int iv_count = mrb_ary_len(mrb, iv_ary); + write_fixnum(ctx, iv_count); + + int i; + for (i = 0; i < iv_count; ++i) + { + mrb_value iv_name = mrb_ary_entry(iv_ary, i); + mrb_value iv_value = mrb_obj_iv_get(mrb, o, mrb_symbol(iv_name)); + + write_value(ctx, iv_name); + write_value(ctx, iv_value); + } +} + +static void +write_userdef(MarshalContext *ctx, mrb_value object) +{ + mrb_state *mrb = ctx->mrb; + struct RObject *o = mrb_obj_ptr(object); + + mrb_value path = mrb_class_path(mrb, o->c); + write_value(ctx, mrb_str_intern(mrb, path)); + + mrb_value dump = mrb_funcall(mrb, object, "_dump", 0); + write_string_value(ctx, dump); +} + +static void +write_value(MarshalContext *ctx, mrb_value value) +{ + mrb_state *mrb = ctx->mrb; + + int arena = mrb_gc_arena_save(mrb); + + if (ctx->objects.contains(value)) + { + ctx->writeByte(TYPE_LINK); + write_fixnum(ctx, ctx->objects.lookupIdx(value)); + mrb_gc_arena_restore(mrb, arena); + return; + } + + switch (mrb_type(value)) + { + case MRB_TT_TRUE : + ctx->writeByte(TYPE_TRUE); + break; + + case MRB_TT_FALSE : + if (mrb_fixnum(value)) + ctx->writeByte(TYPE_FALSE); + else + ctx->writeByte(TYPE_NIL); + break; + + case MRB_TT_FIXNUM : + ctx->writeByte(TYPE_FIXNUM); + write_fixnum(ctx, mrb_fixnum(value)); + break; + + case MRB_TT_FLOAT : + ctx->writeByte(TYPE_FLOAT); + write_float(ctx, mrb_float(value)); + ctx->objects.add(value); + break; + + case MRB_TT_STRING : + ctx->objects.add(value); + ctx->writeByte(TYPE_STRING); + write_string_value(ctx, value); + break; + + case MRB_TT_ARRAY : + ctx->objects.add(value); + ctx->writeByte(TYPE_ARRAY); + write_array(ctx, value); + + break; + + case MRB_TT_HASH : + ctx->objects.add(value); + ctx->writeByte(TYPE_HASH); + write_hash(ctx, value); + + break; + + case MRB_TT_SYMBOL : + if (ctx->symbols.contains(mrb_symbol(value))) + { + ctx->writeByte(TYPE_SYMLINK); + write_fixnum(ctx, ctx->symbols.lookupIdx(mrb_symbol(value))); + ctx->symbols.add(mrb_symbol(value)); + } + else + { + ctx->writeByte(TYPE_SYMBOL); + write_symbol(ctx, value); + } + + break; + + /* Objects seem to have wrong tt */ + case MRB_TT_CLASS : + case MRB_TT_OBJECT : + ctx->objects.add(value); + + if (mrb_obj_respond_to(mrb_obj_ptr(value)->c, + mrb_intern(mrb, "_dump"))) + { + ctx->writeByte(TYPE_USERDEF); + write_userdef(ctx, value); + } + else + { + ctx->writeByte(TYPE_OBJECT); + write_object(ctx, value); + } + + break; + + default : + printf("Warning! Could not write value type '%c'(%d)\n", // FIXME not sure if raise or just print error + mrb_type(value), mrb_type(value)); + fflush(stdout); + } + + mrb_gc_arena_restore(mrb, arena); +} + +static void +writeMarshalHeader(MarshalContext *ctx) +{ + ctx->writeByte(MARSHAL_MAJOR); + ctx->writeByte(MARSHAL_MINOR); +} + +static void +verifyMarshalHeader(MarshalContext *ctx) +{ + char maj = ctx->readByte(); + char min = ctx->readByte(); + + if (maj != MARSHAL_MAJOR || min != MARSHAL_MINOR) + throw Exception(Exception::TypeError, "incompatible marshal file format (can't be read)"); +} + +static mrb_value +marshalDump(mrb_state *mrb, mrb_value) +{ + mrb_value val; + mrb_value port = mrb_nil_value(); + mrb_int limit = 100; + + mrb_get_args(mrb, "o|oi", &val, &port, &limit); + + SDL_RWops *ops; + + bool havePort = mrb_type(port) == MRB_TT_OBJECT; + bool dumpString = false; + mrb_value ret = port; + + if (havePort) + { + FileImpl *file = getPrivateDataCheck(mrb, port, FileType); + ops = file->ops; + } + else + { + ops = SDL_AllocRW(); + RWMemOpen(ops); + dumpString = true; + } + + try + { + MarshalContext ctx; + ctx.mrb = mrb; + ctx.limit = limit; + ctx.ops = ops; + + writeMarshalHeader(&ctx); + write_value(&ctx, val); + } + catch (const Exception &e) + { + if (dumpString) + { + SDL_RWclose(ops); + SDL_FreeRW(ops); + } + + raiseMrbExc(mrb, e); + } + + if (dumpString) + { + int dataSize = RWMemGetData(ops, 0); + + ret = mrb_str_new(mrb, 0, dataSize); + RWMemGetData(ops, RSTRING_PTR(ret)); + + SDL_RWclose(ops); + SDL_FreeRW(ops); + } + + return ret; +} + +static mrb_value +marshalLoad(mrb_state *mrb, mrb_value) +{ + mrb_value port; + + mrb_get_args(mrb, "o", &port); + + SDL_RWops *ops; + + bool freeOps = false; + + if (mrb_type(port) == MRB_TT_OBJECT) + { + FileImpl *file = getPrivateDataCheck(mrb, port, FileType); + ops = file->ops; + } + else if (mrb_string_p(port)) + { + ops = SDL_RWFromConstMem(RSTRING_PTR(port), RSTRING_LEN(port)); + freeOps = true; + } + else + { + qDebug() << "FIXME: Marshal.load: generic IO port not implemented"; + return mrb_nil_value(); + } + + mrb_value val; + + try + { + MarshalContext ctx; + ctx.mrb = mrb; + ctx.ops = ops; + + verifyMarshalHeader(&ctx); + val = read_value(&ctx); + } + catch (const Exception &e) + { + if (freeOps) + SDL_RWclose(ops); + } + + if (freeOps) + SDL_RWclose(ops); + + return val; +} + +void +marshalBindingInit(mrb_state *mrb) +{ + RClass *module = mrb_define_module(mrb, "Marshal"); + + mrb_define_module_function(mrb, module, "dump", marshalDump, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(2)); + mrb_define_module_function(mrb, module, "load", marshalLoad, MRB_ARGS_REQ(1)); +} + +/* Exceptions from these internal functions will be caught higher up */ +void +marshalDumpInt(mrb_state *mrb, SDL_RWops *ops, mrb_value val) +{ + MarshalContext ctx; + ctx.mrb = mrb; + ctx.ops = ops; + ctx.limit = 100; + + writeMarshalHeader(&ctx); + write_value(&ctx, val); +} + +mrb_value +marshalLoadInt(mrb_state *mrb, SDL_RWops *ops) +{ + MarshalContext ctx; + ctx.mrb = mrb; + ctx.ops = ops; + + verifyMarshalHeader(&ctx); + + mrb_value val = read_value(&ctx); + + return val; +} + diff --git a/binding-mruby/mrb-ext/marshal.h b/binding-mruby/mrb-ext/marshal.h new file mode 100644 index 0000000..4af4d7b --- /dev/null +++ b/binding-mruby/mrb-ext/marshal.h @@ -0,0 +1,31 @@ +/* +** marshal.h +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#ifndef MARSHAL_H +#define MARSHAL_H + +#include "mruby.h" +#include "SDL2/SDL_rwops.h" + +void marshalDumpInt(mrb_state *, SDL_RWops *, mrb_value); +mrb_value marshalLoadInt(mrb_state *, SDL_RWops *); + +#endif // MARSHAL_H diff --git a/binding-mruby/mrb-ext/rwmem.cpp b/binding-mruby/mrb-ext/rwmem.cpp new file mode 100644 index 0000000..8290f07 --- /dev/null +++ b/binding-mruby/mrb-ext/rwmem.cpp @@ -0,0 +1,106 @@ +/* +** rwmem.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "rwmem.h" + +#include "SDL2/SDL_rwops.h" +#include + +typedef QVector ByteVec; + +static inline ByteVec * +getRWPrivate(SDL_RWops *ops) +{ + return static_cast(ops->hidden.unknown.data1); +} + +static Sint64 SDL_RWopsSize(SDL_RWops *ops) +{ + ByteVec *v = getRWPrivate(ops); + + return v->size(); +} + +static Sint64 SDL_RWopsSeek(SDL_RWops *, Sint64, int) +{ + /* No need for implementation */ + + return -1; +} + +static size_t SDL_RWopsRead(SDL_RWops *, void *, size_t, size_t) +{ + /* No need for implementation */ + + return 0; +} + +static size_t SDL_RWopsWrite(SDL_RWops *ops, const void *buffer, size_t size, size_t num) +{ + ByteVec *v = getRWPrivate(ops); + + size_t writeBytes = size * num; + + if (writeBytes == 1) + { + char byte = *static_cast(buffer); + v->append(byte); + return 1; + } + + size_t writeInd = v->size(); + v->resize(writeInd + writeBytes); + + memcpy(&(*v)[writeInd], buffer, writeBytes); + + return num; +} + +static int SDL_RWopsClose(SDL_RWops *ops) +{ + ByteVec *v = getRWPrivate(ops); + + delete v; + + return 0; +} + +void RWMemOpen(SDL_RWops *ops) +{ + ByteVec *v = new ByteVec; + ops->hidden.unknown.data1 = v; + + ops->size = SDL_RWopsSize; + ops->seek = SDL_RWopsSeek; + ops->read = SDL_RWopsRead; + ops->write = SDL_RWopsWrite; + ops->close = SDL_RWopsClose; +} + +int RWMemGetData(SDL_RWops *ops, void *buffer) +{ + ByteVec *v = getRWPrivate(ops); + + if (buffer) + memcpy(buffer, v->constData(), v->size()); + + return v->size(); +} diff --git a/binding-mruby/mrb-ext/rwmem.h b/binding-mruby/mrb-ext/rwmem.h new file mode 100644 index 0000000..2f3ed67 --- /dev/null +++ b/binding-mruby/mrb-ext/rwmem.h @@ -0,0 +1,31 @@ +/* +** rwmem.h +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#ifndef RWMEM_H +#define RWMEM_H + +struct SDL_RWops; + +void RWMemOpen(SDL_RWops *ops); + +int RWMemGetData(SDL_RWops *ops, void *buffer); + +#endif // RWMEM_H diff --git a/binding-mruby/mrb-ext/time.cpp b/binding-mruby/mrb-ext/time.cpp new file mode 100644 index 0000000..d8cdb89 --- /dev/null +++ b/binding-mruby/mrb-ext/time.cpp @@ -0,0 +1,224 @@ +/* +** time.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "../binding-util.h" +#include "mruby/string.h" +#include "mruby/array.h" +#include "mruby/class.h" + +#include "time.h" +#include + + +struct TimeImpl +{ + struct tm _tm; + struct timeval _tv; +}; + +extern const mrb_data_type TimeType = +{ + "Time", + freeInstance +}; + +extern mrb_value +timeFromSecondsInt(mrb_state *mrb, time_t seconds) +{ + TimeImpl *p = new TimeImpl; + + p->_tv.tv_sec = seconds; + p->_tm = *localtime(&p->_tv.tv_sec); + + mrb_value obj = wrapObject(mrb, p, TimeType); + + return obj; +} + +static mrb_value +timeAt(mrb_state *mrb, mrb_value) +{ + mrb_int seconds; + mrb_get_args(mrb, "i", &seconds); + + return timeFromSecondsInt(mrb, seconds); +} + +static mrb_value +timeNow(mrb_state *mrb, mrb_value) +{ + TimeImpl *p = new TimeImpl; + + gettimeofday(&p->_tv, 0); + p->_tm = *localtime(&p->_tv.tv_sec); + + mrb_value obj = wrapObject(mrb, p, TimeType); + + return obj; +} + +static mrb_value +secondsAdded(mrb_state *mrb, TimeImpl *p, mrb_int seconds) +{ + TimeImpl *newP = new TimeImpl; + *newP = *p; + + newP->_tv.tv_sec += seconds; + p->_tm = *localtime(&p->_tv.tv_sec); + + return wrapObject(mrb, newP, TimeType); +} + +static mrb_value +timePlus(mrb_state *mrb, mrb_value self) +{ + mrb_int seconds; + mrb_get_args(mrb, "i", &seconds); + + TimeImpl *p = getPrivateData(mrb, self); + + TimeImpl *newP = new TimeImpl; + *newP = *p; + + newP->_tv.tv_sec += seconds; + p->_tm = *localtime(&p->_tv.tv_sec); + + return wrapObject(mrb, newP, TimeType); +} + +static mrb_value +timeMinus(mrb_state *mrb, mrb_value self) +{ + mrb_value minuent; + mrb_get_args(mrb, "o", &minuent); + + TimeImpl *p = getPrivateData(mrb, self); + + if (mrb_fixnum_p(minuent)) + { + mrb_int seconds = mrb_fixnum(minuent); + + return secondsAdded(mrb, p, -seconds); + } + else if (mrb_type(minuent) == MRB_TT_OBJECT) + { + TimeImpl *o = getPrivateDataCheck(mrb, minuent, TimeType); + + return mrb_float_value(mrb, p->_tv.tv_sec - o->_tv.tv_sec); + } + else + { + mrb_raisef(mrb, E_TYPE_ERROR, "Expected Fixnum or Time, got %S", + mrb_str_new_cstr(mrb, mrb_class_name(mrb, mrb_class(mrb, minuent)))); + } + + return mrb_nil_value(); +} + +static mrb_value +timeCompare(mrb_state *mrb, mrb_value self) +{ + mrb_value cmpTo; + mrb_get_args(mrb, "o", &cmpTo); + + mrb_int cmpToS = 0; + + if (mrb_fixnum_p(cmpTo)) + { + cmpToS = mrb_fixnum(cmpTo); + } + else if (mrb_type(cmpTo) == MRB_TT_OBJECT) + { + TimeImpl *cmpToP = getPrivateDataCheck(mrb, cmpTo, TimeType); + cmpToS = cmpToP->_tv.tv_sec; + } + else + { + mrb_raisef(mrb, E_TYPE_ERROR, "Expected Fixnum or Time, got %S", + mrb_str_new_cstr(mrb, mrb_class_name(mrb, mrb_class(mrb, cmpTo)))); + } + + TimeImpl *p = getPrivateData(mrb, self); + mrb_int selfS = p->_tv.tv_sec; + + if (selfS < cmpToS) + return mrb_fixnum_value(-1); + else if (selfS == cmpToS) + return mrb_fixnum_value(0); + else + return mrb_fixnum_value(1); +} + +static mrb_value +timeStrftime(mrb_state *mrb, mrb_value self) +{ + const char *format; + mrb_get_args(mrb, "z", &format); + + TimeImpl *p = getPrivateData(mrb, self); + + static char buffer[512]; + strftime(buffer, sizeof(buffer), format, &p->_tm); + + return mrb_str_new_cstr(mrb, buffer); +} + +#define TIME_ATTR(attr) \ + static mrb_value \ + timeGet_##attr(mrb_state *mrb, mrb_value self) \ + { \ + TimeImpl *p = getPrivateData(mrb, self); \ + return mrb_fixnum_value(p->_tm.tm_##attr); \ + } + +TIME_ATTR(sec) +TIME_ATTR(min) +TIME_ATTR(hour) +TIME_ATTR(mday) +TIME_ATTR(mon) +TIME_ATTR(year) +TIME_ATTR(wday) + +#define TIME_BIND_ATTR(attr) \ + { mrb_define_method(mrb, klass, #attr, timeGet_##attr, MRB_ARGS_NONE()); } + +void +timeBindingInit(mrb_state *mrb) +{ + RClass *klass = mrb_define_class(mrb, "Time", 0); + mrb_include_module(mrb, klass, mrb_class_get(mrb, "Comparable")); + + mrb_define_class_method(mrb, klass, "now", timeNow, MRB_ARGS_NONE()); + mrb_define_class_method(mrb, klass, "at", timeAt, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, klass, "+", timePlus, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, klass, "-", timeMinus, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, klass, "<=>", timeCompare, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, klass, "strftime", timeStrftime, MRB_ARGS_REQ(1)); + + TIME_BIND_ATTR(sec); + TIME_BIND_ATTR(min); + TIME_BIND_ATTR(hour); + TIME_BIND_ATTR(mday); + TIME_BIND_ATTR(mon); + TIME_BIND_ATTR(year); + TIME_BIND_ATTR(wday); +}