Last active
January 1, 2016 15:58
-
-
Save Qix-/8167320 to your computer and use it in GitHub Desktop.
A simple Lua module that converts certain (supported) codes to equivalent Win32 console outputs. UTF/wide inputs are not supported. It is installed to `io.write` as an override.
This file contains 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
/* | |
* WANSI - The Windows ANSI Lua Module | |
*/ | |
#ifndef _WIN32 | |
static_assert(false, "This Lua module is only intended to be built by MSVC tools on Windows."); | |
#endif | |
#include <windows.h> | |
#include <lua.hpp> | |
BOOL APIENTRY DllMain(HANDLE module, DWORD reason, LPVOID reserved) { return TRUE; } | |
/***************************************************/ | |
// Some nifty macros | |
#define ERRORMSG(L, fmt, ...) {\ | |
LPSTR buf = 0;\ | |
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, GetLastError(), 0, (LPSTR)&buf, 0, 0);\ | |
luaL_error(L, fmt ": %s", __VA_ARGS__, buf);\ | |
LocalFree(buf);} | |
#define XNOT(a, b) ~(~(a) | b) | |
#define STRIPCOL(mods) XNOT(mods, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) | |
#define STRIPCOLB(mods) XNOT(mods, BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) | |
// Set up state | |
static char last = (char) -1; | |
static bool escaped = false; | |
static int curCode = -1; | |
static WORD modifiers = 0; | |
/** | |
* ANSI code 'visitor' that manages | |
* the current output state given ANSI | |
* escape codes | |
*/ | |
void handleAnsi(lua_State* L, int value, int section, int character) | |
{ | |
// Switch | |
switch(value) | |
{ | |
case 30: modifiers = STRIPCOL(modifiers); break; | |
case 31: modifiers = STRIPCOL(modifiers) | FOREGROUND_RED; break; | |
case 32: modifiers = STRIPCOL(modifiers) | FOREGROUND_GREEN; break; | |
case 33: modifiers = STRIPCOL(modifiers) | FOREGROUND_RED | FOREGROUND_GREEN; break; | |
case 34: modifiers = STRIPCOL(modifiers) | FOREGROUND_BLUE; break; | |
case 35: modifiers = STRIPCOL(modifiers) | FOREGROUND_RED | FOREGROUND_BLUE; break; | |
case 36: modifiers = STRIPCOL(modifiers) | FOREGROUND_BLUE | FOREGROUND_GREEN; break; | |
case 37: modifiers = STRIPCOL(modifiers) | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED; break; | |
case 40: modifiers = STRIPCOLB(modifiers); break; | |
case 41: modifiers = STRIPCOLB(modifiers) | BACKGROUND_RED; break; | |
case 42: modifiers = STRIPCOLB(modifiers) | BACKGROUND_GREEN; break; | |
case 43: modifiers = STRIPCOLB(modifiers) | BACKGROUND_RED | BACKGROUND_GREEN; break; | |
case 44: modifiers = STRIPCOLB(modifiers) | BACKGROUND_BLUE; break; | |
case 45: modifiers = STRIPCOLB(modifiers) | BACKGROUND_RED | BACKGROUND_BLUE; break; | |
case 46: modifiers = STRIPCOLB(modifiers) | BACKGROUND_BLUE | BACKGROUND_GREEN; break; | |
case 47: modifiers = STRIPCOLB(modifiers) | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED; break; | |
case 39: | |
case 0: modifiers |= FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED; break; | |
case 49: modifiers = STRIPCOLB(modifiers); break; | |
case 1: modifiers |= FOREGROUND_INTENSITY; break; | |
case 22: modifiers = XNOT(modifiers, FOREGROUND_INTENSITY); break; | |
case 4: modifiers |= COMMON_LVB_UNDERSCORE; break; | |
case 24: modifiers = XNOT(modifiers, COMMON_LVB_UNDERSCORE); break; | |
case 7: modifiers |= COMMON_LVB_REVERSE_VIDEO; break; | |
case 27: modifiers = XNOT(modifiers, COMMON_LVB_REVERSE_VIDEO); break; | |
default: | |
luaL_error(L, "wansi: unsupported ANSI escape code: %d <%d, %d>", (int)value, character, section); | |
break; | |
} | |
// Update attribute | |
if(!SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), modifiers)) | |
ERRORMSG(L, "could not update attributes <%d, %d>", character, section); | |
// Reset code | |
curCode = -1; | |
} | |
/** | |
* io.write() replacement function | |
*/ | |
int wansi_write(lua_State* L) | |
{ | |
// Get number of upvalues | |
int n = lua_gettop(L); | |
// Iterate | |
for(int i = 1; i <= n; i++) | |
{ | |
// Get string | |
size_t size; | |
const char* str = lua_tolstring(L, i, &size); | |
// Iterate | |
for(int ci = 0; ci < size; ci++) | |
{ | |
// Get char | |
char c = str[ci]; | |
// Escape? | |
if(c == '\x1b') | |
{ | |
// Already escaped? | |
if(escaped || last == '\x1b') | |
luaL_error(L, "invalid ANSI escape sequence; opcode expected, escape character (x1B) discovered <%d, %d>", ci, i); | |
goto parseNext; | |
} | |
// End of escape code? | |
else if(c == 'm' && escaped) | |
{ | |
// No code was given? (i.e. \x[1;m which is invalid) | |
if(curCode == -1) | |
luaL_error(L, "invalid ANSI escape sequence; opcode expected, end-of-sequence ('m') discovered <%d, %d>", ci, i); | |
// Visit | |
handleAnsi(L, curCode, i, ci); | |
// Unflag | |
escaped = false; | |
goto parseNext; | |
} | |
// Open bracket? | |
else if(c == '[' && last == '\x1b') | |
{ | |
// Flag | |
escaped = true; | |
goto parseNext; | |
} | |
// Semi? | |
else if(c == ';' && escaped) | |
{ | |
// No code was given? (i.e. \x[;1m which is invalid) | |
if(curCode == -1) | |
luaL_error(L, "invalid ANSI escape sequence; opcode expected, code separator (';') discovered <%d, %d>", ci, i); | |
// Visit | |
handleAnsi(L, curCode, i, ci); | |
// Continue | |
goto parseNext; | |
} | |
// Number? | |
else if(escaped && c >= '0' && c <= '9') | |
{ | |
// Add to it | |
if(curCode == -1) | |
curCode = 0; | |
curCode *= 10; | |
curCode += c - '0'; | |
goto parseNext; | |
} | |
// Are we escaped and nothing above applies? | |
else if(escaped || last == '\x1b') | |
// Nope! | |
luaL_error(L, "invalid ANSI escape sequence; unexpected character '%c' <%d, %d>", c, ci, i); | |
// Write | |
DWORD out; | |
if(!WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), &c, 1, &out, NULL)) | |
ERRORMSG(L, "I/O error when writing to console <%d, %d>", ci, i); | |
parseNext: | |
// Store last | |
last = c; | |
} | |
} | |
// Return STDOut (this mimics the original io.write for stringing) | |
lua_getglobal(L, "io"); | |
lua_getfield(L, -1, "stdout"); | |
lua_remove(L, -2); | |
return 1; | |
} | |
/** | |
* Entry point for the Lua module | |
*/ | |
extern "C" __declspec(dllexport) int luaopen_wansi(lua_State* L) | |
{ | |
// Get io | |
lua_getglobal(L, "io"); | |
if(lua_isnil(L, -1)) | |
return luaL_error(L, "global 'io' does not exist; WANSI cannot install"); | |
// Get write | |
lua_getfield(L, -1, "write"); | |
if(!lua_isfunction(L, -1)) | |
return luaL_error(L, "'io.write' does not point to a function; WANSI cannot install"); | |
// Re-assign | |
lua_setfield(L, -2, "rawwrite"); | |
// Push our function | |
lua_pushcfunction(L, wansi_write); | |
// Install | |
lua_setfield(L, -2, "write"); | |
// Signal WANSI has been installed | |
lua_pushboolean(L, 1); | |
lua_setfield(L, -2, "wansi"); | |
// Clean Up | |
lua_pop(L, 1); | |
// Return true | |
lua_pushboolean(L, 1); | |
return 1; | |
} | |
/***************************************************/ |
Include with:
-- Through package loader
fn, err = package.load('<PATH_TO_DLL>\\wansi.dll', 'luaopen_wansi')
if not fn or err then error(err or "could not install WANSI; unknown error") end
fn()
assert(io.wansi) -- Optional
-- Through C-loader
package.cpath = package.cpath .. ";<PATH_TO_DLL>\\?.dll"
require "wansi"
assert(io.wansi) -- Optional
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Compile with: