Last active
July 18, 2016 03:54
-
-
Save ggcrunchy/d01d0487cdd77fc9006ac480266dfa76 to your computer and use it in GitHub Desktop.
An attempt at a nice byte-reading interface, for Lua-C/C++ interop in various projects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files (the | |
* "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* | |
* [ MIT license: http://www.opensource.org/licenses/mit-license.php ] | |
*/ | |
#include "ByteReader.h" | |
// Constructor | |
ByteReader::ByteReader (lua_State * L, int arg, bool bReplace) : mBytes(NULL), mForbidden(false) | |
{ | |
// Take the object's length as a best guess of the byte count. If the object is a string, | |
// these are exactly the bytes we want. Otherwise, look up the __bytes metafield. | |
mCount = lua_objlen(L, arg); | |
if (lua_isstring(L, arg)) mBytes = lua_tostring(L, arg); | |
else | |
{ | |
if (arg < 0 && -arg <= lua_gettop(L)) arg = lua_gettop(L) + arg + 1; // account for negative indices in stack | |
if (luaL_getmetafield(L, arg, "__bytes")) // ...[, __bytes] | |
{ | |
LookupBytes(L, arg); | |
if (bReplace && mBytes) lua_replace(L, arg); // ..., bytes, ... | |
} | |
else PushError(L, "Unable to read bytes from %s at index %i", arg); // ..., err | |
} | |
} | |
// Try to get bytes from an object's __bytes metafield | |
void ByteReader::LookupBytes (lua_State * L, int arg) | |
{ | |
const char * names[] = { "forbidden", "vector", "uchar", "none", NULL }; | |
int options[] = { eForbidden, eVector, eUchar, eNone }; | |
int res = options[luaL_checkoption(L, arg, "none", names)]; | |
if (res == eForbidden) | |
{ | |
mForbidden = true; | |
PushError(L, "Byte-reading explicitly forbidden from %s at index %i", arg); // ..., __bytes, err | |
} | |
else if (!lua_isfunction(L, -1)) PointToBytes(L, arg, res); | |
else | |
{ | |
lua_pushvalue(L, arg); // ..., __bytes, object | |
if (lua_pcall(L, 1, 1, 0) == 0) // ..., bytes / false[, err] | |
{ | |
ByteReader result(L, -1, false); | |
mBytes = result.mBytes; | |
mCount = result.mCount; | |
} | |
} | |
} | |
// Point to the userdata's bytes, possibly at an offset | |
void ByteReader::PointToBytes (lua_State * L, int arg, int option) | |
{ | |
if (lua_type(L, arg) == LUA_TUSERDATA) | |
{ | |
if (option == eVector) | |
{ | |
#ifndef BYTE_READER_NO_VECTOR | |
auto vec = (std::vector<unsigned char> *)lua_touserdata(L, arg); | |
mBytes = &vec[0]; | |
mCount = vec->size(); | |
#else | |
PushError(L, "Vector support is disabled"); | |
#endif | |
} | |
else if (option == eUchar) | |
{ | |
auto uchar = (ByteReaderUchar *)lua_touserdata(L, arg); | |
mBytes = uchar->mBytes; | |
mCount = uchar->mCount; | |
} | |
else | |
{ | |
lua_Integer offset = luaL_optinteger(L, -1, 0); | |
if (offset < 0 || size_t(offset) > mCount) PushError(L, "Offset of %s at index %i fails bounds check", arg); | |
else | |
{ | |
mBytes = ((unsigned char *)lua_touserdata(L, arg)) + offset; | |
mCount -= offset; | |
} | |
} | |
} | |
else PushError(L, "Cannot point to %s at index %i", arg); | |
} | |
// Wrapper for common error-pushing pattern | |
void ByteReader::PushError (lua_State * L, const char * format, int arg) | |
{ | |
lua_pushfstring(L, format, luaL_typename(L, arg), arg); // ..., err | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files (the | |
* "Software"), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* | |
* [ MIT license: http://www.opensource.org/licenses/mit-license.php ] | |
*/ | |
#ifndef BYTE_READER_H | |
#define BYTE_READER_H | |
extern "C" { | |
#include "lua.h" | |
#include "lauxlib.h" | |
} | |
#ifndef BYTE_READER_NO_VECTOR | |
#include <vector> | |
#endif | |
struct ByteReaderUchar { | |
unsigned char * mBytes; // Stream of bytes | |
size_t mCount; // Byte count | |
}; | |
class ByteReader { | |
public: | |
const void * mBytes; // Resolved byte stream | |
size_t mCount; // Number of bytes available in stream | |
bool mForbidden; // Byte-reading explicitly disallowed? | |
ByteReader (lua_State * L, int arg, bool bReplace = true); | |
enum { eForbidden, eVector, eUchar, eNone }; | |
private: | |
void LookupBytes (lua_State * L, int arg); | |
void PointToBytes (lua_State * L, int arg, int option); | |
void PushError (lua_State * L, const char * format, int arg); | |
}; | |
// A `ByteReader` transforms Lua inputs adhering to a simple protocol into | |
// a bytes-count pair that other C and C++ APIs are able to safely consume. | |
// The protocol goes as follows: | |
// | |
// The object at index `arg` is examined. | |
// | |
// If it happens to be a string, we can use its bytes and length directly. | |
// | |
// Failing that, the object's metatable (if it has one) is queried for field | |
// **__bytes**. Should no value at all be found, the object clearly does not | |
// honor the protocol, so we quit. | |
// | |
// When **__bytes** has a value of **"forbidden"**, the object's type is | |
// understood to say "byte-reading is specifically disallowed". In this | |
// situation, there is nothing more we can do, so we quit; the reader's | |
// **mForbidden** member will be **true** to indicate the special | |
// circumstances. | |
// | |
// If access is allowed, we next check whether the object is a full userdata. | |
// If so, there are a few possibilities: | |
// | |
// - **__bytes** has a value of **"uchar"**: the userdata's first bytes contain | |
// a `ByteReaderUchar` struct, with | |
// **mBytes** and **mCount** members. | |
// | |
// - **__bytes** has a value of **"vector"**: the userdata's first bytes hold a | |
// `std::vector<unsigned char>`. Its contents supply the bytes, with `size()` | |
// as the count. **BYTE\_READER\_NO\_VECTOR** may be defined to disable the | |
// vector code path. | |
// | |
// - Last but not least, we use the userdata's bytes and length directly, as | |
// done with strings. When **__bytes** contains an integer, it is assumed to be | |
// an offset (between 0 and the length minus 1) where valid bytes begin, with | |
// the final length shortened accordingly. | |
// | |
// If the object was not a full userdata, **__bytes** must be a function, to be | |
// called as | |
// object = func(object) | |
// The process (i.e. is the object a string? if not, does it have a **__bytes** | |
// metafield? et al.) then recurses on this new object and uses its result | |
// instead. | |
// | |
// When bytes are successfully found, the reader's **mBytes** member will point | |
// to them, with the byte count stored in **mCount**. If `bReplace` is **true** | |
// (the default), the bytes (either a string or full userdata) are moved into | |
// slot `arg`, overwriting the original object. | |
// | |
// Should an error happen along the way, **mBytes** will be NULL and an error | |
// message will be pushed on top of the stack. | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment