Skip to content

Instantly share code, notes, and snippets.

@Qix-
Last active January 1, 2016 15:58
Show Gist options
  • Save Qix-/8167320 to your computer and use it in GitHub Desktop.
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.
/*
* 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;
}
/***************************************************/
@Qix-
Copy link
Author

Qix- commented Dec 29, 2013

Compile with:

REM Optional; just need an MSVC environment.
call "C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"

REM Compile
cl /LD /O2 /EHsc -I"<LUA_DIR>\include" /nologo wansi.cpp /link /nodefaultlib /entry:DllMain user32.lib kernel32.lib "<LUA_DIR>\liblua52.a"

@Qix-
Copy link
Author

Qix- commented Dec 29, 2013

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