Skip to content

Instantly share code, notes, and snippets.

@CapsAdmin
Created October 29, 2018 05:31
Show Gist options
  • Save CapsAdmin/e63da076fa851dffbe83ed3bfbabfbc3 to your computer and use it in GitHub Desktop.
Save CapsAdmin/e63da076fa851dffbe83ed3bfbabfbc3 to your computer and use it in GitHub Desktop.
lua repl using ffi, windows isn't finished/working
local ffi = require("ffi")
local start
local stop
local read
if jit.os == "Linux" then
ffi.cdef([[
struct termios
{
unsigned int c_iflag; /* input mode flags */
unsigned int c_oflag; /* output mode flags */
unsigned int c_cflag; /* control mode flags */
unsigned int c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[32]; /* control characters */
unsigned int c_ispeed; /* input speed */
unsigned int c_ospeed; /* output speed */
};
int tcgetattr(int __fd, struct termios *__termios_p);
int tcsetattr(int __fd, int __optional_actions, const struct termios *__termios_p);
int usleep(uint32_t);
]])
local ISIG = 0000001
local ICANON = 0000002
local ECHO = 0000010
local VMIN = 6
local VTIME = 5
local TCSANOW = 0
local stdin = 0
local old_attributes
start = function()
if not old_attributes then
old_attributes = ffi.new("struct termios[1]")
ffi.C.tcgetattr(stdin, old_attributes)
end
local attr = ffi.new("struct termios[1]")
ffi.C.tcgetattr(stdin, attr)
attr[0].c_lflag = bit.band(attr[0].c_lflag, bit.bnot(bit.bor(ICANON, ECHO, ISIG)))
attr[0].c_cc[VMIN] = 0
attr[0].c_cc[VTIME] = 0
ffi.C.tcsetattr(stdin, TCSANOW, attr)
end
read = function()
return io.read()
end
stop = function()
ffi.C.tcsetattr(stdin, TCSANOW, old_attributes)
old_attributes = nil
end
end
if jit.os == "Windows" then
ffi.cdef([[
struct COORD {
short X;
short Y;
};
struct KEY_EVENT_RECORD {
int bKeyDown;
unsigned short wRepeatCount;
unsigned short wVirtualKeyCode;
unsigned short wVirtualScanCode;
union {
wchar_t UnicodeChar;
char AsciiChar;
} uChar;
unsigned long dwControlKeyState;
};
struct MOUSE_EVENT_RECORD {
struct COORD dwMousePosition;
unsigned long dwButtonState;
unsigned long dwControlKeyState;
unsigned long dwEventFlags;
};
struct WINDOW_BUFFER_SIZE_RECORD {
struct COORD dwSize;
};
struct MENU_EVENT_RECORD {
unsigned int dwCommandId;
};
struct FOCUS_EVENT_RECORD {
int bSetFocus;
};
struct INPUT_RECORD {
unsigned short EventType;
union {
struct KEY_EVENT_RECORD KeyEvent;
struct MOUSE_EVENT_RECORD MouseEvent;
struct WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
struct MENU_EVENT_RECORD MenuEvent;
struct FOCUS_EVENT_RECORD FocusEvent;
} Event;
};
int PeekConsoleInputA(
void* hConsoleInput,
struct INPUT_RECORD* lpBuffer,
unsigned long nLength,
unsigned long * lpNumberOfEventsRead
);
int ReadConsoleInputA(
void* hConsoleInput,
struct INPUT_RECORD* lpBuffer,
unsigned long nLength,
unsigned long * lpNumberOfEventsRead
);
void* GetStdHandle(unsigned long nStdHandle);
int PeekNamedPipe(
void* hNamedPipe,
void* lpBuffer,
unsigned long nBufferSize,
unsigned long* lpBytesRead,
unsigned long* lpTotalBytesAvail,
unsigned long* lpBytesLeftThisMessage
);
]])
local STD_INPUT_HANDLE = -10
local stdin = ffi.C.GetStdHandle(STD_INPUT_HANDLE)
if true then
read = function()
local size = ffi.new("unsigned long[1]")
ffi.C.PeekNamedPipe(stdin, nil,nil,nil, size, nil)
if size[0] > 0 then
return io.read()
end
end
else
local rec = ffi.new("struct INPUT_RECORD[1]")
local events = ffi.new("unsigned long[1]")
read = function()
ffi.C.PeekConsoleInputA(stdin, rec, ffi.sizeof(rec), events)
if events[0] > 0 then
for i = 1, tonumber(events[0]) do
ffi.C.ReadConsoleInputA(stdin, rec, ffi.sizeof(rec), events)
local info = rec[0]
if info.EventType == 1 and info.Event.KeyEvent.bKeyDown then
return string.char(info.Event.KeyEvent.uChar.UnicodeChar), info.Event.KeyEvent
end
end
end
end
end
start = function() end
stop = function() end
end
local time = os.clock() + math.random() + 1
local buffer = ""
local pos = 0
start()
local function get_caret_pos()
io.write("\x1b[6n")
while true do
local str = read()
if str and str:sub(1, 2) == "\27[" then
y,x = str:match("\27%[(%d+);(%d+)R")
break
end
end
return tonumber(x), tonumber(y)
end
local function set_caret_pos(x, y)
io.write("\27[",y,";",x,"f")
end
local function move_caret(ox, oy)
local x,y = get_caret_pos()
set_caret_pos(x + ox, y + oy)
end
local function push_caret()
io.write("\27[s")
end
local function pop_caret()
io.write("\27[u")
end
local function get_size()
push_caret()
set_caret_pos(99999999,99999999)
local w,h = get_caret_pos()
pop_caret()
return w,h
end
local function write_string(x, y, str)
io.write("\27[s ", "\27[", y, ";", x, "H", "\27[K", str, "\27[u")
end
local function erase_line(y)
push_caret()
set_caret_pos(0, y)
io.write("\27[K")
pop_caret()
end
local function set_fg_color(r,g,b)
r = math.floor(r * 255)
g = math.floor(g * 255)
b = math.floor(b * 255)
io.write("\27[38;2;", r, ";", g, ";", b, "m")
end
local function set_bg_color(r,g,b)
r = math.floor(r * 255)
g = math.floor(g * 255)
b = math.floor(b * 255)
io.write("\27[48;2;", r, ";", g, ";", b, "m")
end
local function find_next_word(buffer, x, dir)
local left = dir == "left" and buffer:sub(0, x-1):reverse() or buffer:sub(x+1, -1)
if left:find("^%s", 0) then
return left:find("%S")
elseif left:find("^%p", 0) then
return left:find("%P", 0)
end
return left:find("%s", 0) or left:find("%p", 0) or #left + 1
end
local x, y = get_caret_pos()
while true do
ffi.C.usleep(500)
-- buffer = ("="):rep((math.sin(os.clock() * 10) * 0.5 + 0.5) * 50)
write_string(0, y, buffer)
local str = read()
if str then
local w, h = get_size()
local x, y = get_caret_pos()
if str == "" then -- newline/enter?
erase_line(y)
set_caret_pos(0, y + 1)
io.write("> ", buffer, "\n")
local func, err = loadstring(buffer)
if func then
local func, res = pcall(func)
if not func then
set_fg_color(1,0,0)
io.write(res, "\n")
set_fg_color(1,1,1)
end
else
set_fg_color(1,0,0)
io.write(err, "\n")
set_fg_color(1,1,1)
end
buffer = ""
set_caret_pos(0, y + 1)
elseif str:byte() >= 32 and str:byte() < 127 then -- asci chars
buffer = buffer:sub(0, x-1) .. str .. buffer:sub(x+#str-1, -1)
move_caret(#str, 0)
elseif str:sub(1,2) == "\27[" then
local seq = str:sub(3, #str)
if seq == "3~" then -- delete
buffer = buffer:sub(0, x-1) .. buffer:sub(x+1, -1)
elseif seq == "D" then -- left
move_caret(-1, 0)
elseif seq == "C" then -- right
move_caret(1, 0)
elseif seq == "H" then -- home
set_caret_pos(1, y)
elseif seq == "F" then -- end
set_caret_pos(#buffer + 1, y)
elseif seq == "1;5C" then -- ctrl right
local offset = find_next_word(buffer, x, "right")
if offset then
move_caret(offset + 1, 0)
end
elseif seq == "1;5D" then -- ctrl left
local offset = find_next_word(buffer, x, "left")
if offset then
move_caret(-offset + 1, 0)
end
else
print("ansi escape sequence: " .. seq)
end
else
if #str == 1 then
local byte = str:byte()
if byte == 3 then -- ctrl c
io.write("\nquit\n")
break
elseif byte == 127 then -- backspace
buffer = buffer:sub(0, math.max(x - 1 - 1, 0)) .. buffer:sub(x, -1)
move_caret(-1, 0)
elseif byte == 23 then -- ctrl backspace
local offset = find_next_word(buffer, x, "left")
if offset then
buffer = buffer:sub(0, x - offset) .. buffer:sub(x, -1)
end
else
print("byte: " .. byte)
end
elseif str:byte() < 127 then
if str == "\27\68" then -- ctrl delete
local offset = find_next_word(buffer, x, "right")
if offset then
buffer = buffer:sub(0, x - 1) .. buffer:sub(x + offset, -1)
end
else
print("char sequence: " .. table.concat({str:byte(1, #str)}, ", ") .. " (" .. #str .. ")")
end
else -- unicode ?
--buffer = buffer:sub(0, x-1) .. str .. buffer:sub(x+#str-1, -1)
--move_caret(#str, 0)
end
end
local x, y = get_caret_pos()
x = math.min(x, #buffer + 1)
set_caret_pos(x, y)
end
if time < os.clock() then
time = os.clock() + math.random() + 1
-- print("some text!")
end
end
stop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment