mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-01-18 02:26:44 +00:00
Added http.request to Lua, renamed Lua.PushValue to Lua.PushObject, added lua.lua REPL
This commit is contained in:
parent
c4d5ebd500
commit
9d978fd9ab
12 changed files with 282 additions and 37 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -27,7 +27,8 @@ x86/
|
|||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
/[Bb]in/
|
||||
/[Cc]apy64/bin/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
|
|
|
@ -11,6 +11,6 @@ namespace Capy64.API;
|
|||
public interface IPlugin
|
||||
{
|
||||
void ConfigureServices(IServiceCollection services) { }
|
||||
void LuaInit(Lua state) { }
|
||||
void LuaInit(Lua L) { }
|
||||
|
||||
}
|
||||
|
|
78
Capy64/Assets/Lua/bin/lua.lua
Normal file
78
Capy64/Assets/Lua/bin/lua.lua
Normal file
|
@ -0,0 +1,78 @@
|
|||
local term = require("term")
|
||||
local io = require("io")
|
||||
local colors = require("colors")
|
||||
local colours = colors
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
print("This is an interactive Lua prompt.")
|
||||
print("To run a lua program, just type its name.")
|
||||
return
|
||||
end
|
||||
|
||||
--local pretty = require "cc.pretty"
|
||||
|
||||
local bRunning = true
|
||||
local tCommandHistory = {}
|
||||
local tEnv = {
|
||||
["exit"] = setmetatable({}, {
|
||||
__tostring = function() return "Call exit() to exit." end,
|
||||
__call = function() bRunning = false end,
|
||||
}),
|
||||
["_echo"] = function(...)
|
||||
return ...
|
||||
end,
|
||||
}
|
||||
setmetatable(tEnv, { __index = _ENV })
|
||||
|
||||
for k, v in pairs(package.loaded) do
|
||||
tEnv[k] = v
|
||||
end
|
||||
|
||||
term.setForeground(colours.yellow)
|
||||
print("Interactive Lua prompt.")
|
||||
print("Call exit() to exit.")
|
||||
term.setForeground(colours.white)
|
||||
|
||||
while bRunning do
|
||||
term.setForeground( colours.yellow )
|
||||
write("lua> ")
|
||||
term.setForeground( colours.white )
|
||||
|
||||
local s = io.read(nil, tCommandHistory)
|
||||
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
||||
table.insert(tCommandHistory, s)
|
||||
end
|
||||
|
||||
local nForcePrint = 0
|
||||
local func, e = load(s, "=lua", "t", tEnv)
|
||||
local func2 = load("return _echo(" .. s .. ");", "=lua", "t", tEnv)
|
||||
if not func then
|
||||
if func2 then
|
||||
func = func2
|
||||
e = nil
|
||||
nForcePrint = 1
|
||||
end
|
||||
else
|
||||
if func2 then
|
||||
func = func2
|
||||
end
|
||||
end
|
||||
|
||||
if func then
|
||||
local tResults = table.pack(pcall(func))
|
||||
if tResults[1] then
|
||||
local n = 1
|
||||
while n < tResults.n or n <= nForcePrint do
|
||||
local value = tResults[n + 1]
|
||||
print(tostring(value))
|
||||
n = n + 1
|
||||
end
|
||||
else
|
||||
print(tResults[2])
|
||||
end
|
||||
else
|
||||
print(e)
|
||||
end
|
||||
|
||||
end
|
|
@ -10,4 +10,4 @@ term.setPos(1,1)
|
|||
|
||||
print(os.version())
|
||||
|
||||
dofile("/bin/shell.lua")
|
||||
dofile("/bin/lua.lua")
|
|
@ -144,7 +144,7 @@ public class Bios : IPlugin
|
|||
|
||||
public static void InstallOS(bool force = false)
|
||||
{
|
||||
var installedFilePath = Path.Combine(FileSystem.BasePath, ".installed");
|
||||
var installedFilePath = Path.Combine(Capy64.AppDataPath, ".installed");
|
||||
if (!File.Exists(installedFilePath) || force)
|
||||
{
|
||||
FileSystem.CopyDirectory("Assets/Lua", FileSystem.DataPath, true, true);
|
||||
|
|
|
@ -15,12 +15,18 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Capy64.Utils;
|
||||
using System.IO;
|
||||
|
||||
namespace Capy64;
|
||||
|
||||
public class Capy64 : Game, IGame
|
||||
{
|
||||
public const string Version = "Capy64 a0.0.1";
|
||||
public const string Version = "a0.0.1";
|
||||
public static string AppDataPath = Path.Combine(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData,
|
||||
Environment.SpecialFolderOption.Create),
|
||||
"Capy64");
|
||||
public Game Game => this;
|
||||
public IList<IPlugin> NativePlugins { get; private set; }
|
||||
public IList<IPlugin> Plugins { get; private set; }
|
||||
|
@ -102,7 +108,7 @@ public class Capy64 : Game, IGame
|
|||
|
||||
protected override void Initialize()
|
||||
{
|
||||
Window.Title = Version;
|
||||
Window.Title = "Capy64 " + Version;
|
||||
|
||||
UpdateSize();
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Capy64;
|
|||
|
||||
public interface IGame
|
||||
{
|
||||
|
||||
Game Game { get; }
|
||||
IList<IPlugin> NativePlugins { get; }
|
||||
IList<IPlugin> Plugins { get; }
|
||||
|
|
|
@ -11,31 +11,31 @@ using System.Threading.Tasks;
|
|||
namespace Capy64.LuaRuntime.Extensions;
|
||||
public static class Utils
|
||||
{
|
||||
public static void PushArray(this Lua state, object obj)
|
||||
public static void PushArray(this Lua L, object obj)
|
||||
{
|
||||
var iterable = obj as IEnumerable;
|
||||
|
||||
state.NewTable();
|
||||
L.NewTable();
|
||||
long i = 1;
|
||||
foreach (var item in iterable)
|
||||
{
|
||||
state.PushValue(item);
|
||||
state.RawSetInteger(-2, i++);
|
||||
L.PushObject(item);
|
||||
L.RawSetInteger(-2, i++);
|
||||
}
|
||||
state.SetTop(-1);
|
||||
L.SetTop(-1);
|
||||
}
|
||||
#nullable enable
|
||||
public static int PushValue(this Lua state, object? obj)
|
||||
public static int PushObject(this Lua L, object? obj)
|
||||
{
|
||||
var type = obj?.GetType();
|
||||
switch (obj)
|
||||
{
|
||||
case string str:
|
||||
state.PushString(str);
|
||||
L.PushString(str);
|
||||
break;
|
||||
|
||||
case char:
|
||||
state.PushString(obj.ToString());
|
||||
L.PushString(obj.ToString());
|
||||
break;
|
||||
|
||||
case byte:
|
||||
|
@ -45,37 +45,37 @@ public static class Utils
|
|||
case int:
|
||||
case uint:
|
||||
case double:
|
||||
state.PushNumber(Convert.ToDouble(obj));
|
||||
L.PushNumber(Convert.ToDouble(obj));
|
||||
break;
|
||||
|
||||
case long l:
|
||||
state.PushInteger(l);
|
||||
L.PushInteger(l);
|
||||
break;
|
||||
|
||||
case bool b:
|
||||
state.PushBoolean(b);
|
||||
L.PushBoolean(b);
|
||||
break;
|
||||
|
||||
case null:
|
||||
state.PushNil();
|
||||
L.PushNil();
|
||||
break;
|
||||
|
||||
case byte[] b:
|
||||
state.PushBuffer(b);
|
||||
L.PushBuffer(b);
|
||||
break;
|
||||
|
||||
|
||||
case LuaFunction func:
|
||||
state.PushCFunction(func);
|
||||
L.PushCFunction(func);
|
||||
break;
|
||||
|
||||
case IntPtr ptr:
|
||||
state.PushLightUserData(ptr);
|
||||
L.PushLightUserData(ptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (type is not null && type.IsArray)
|
||||
{
|
||||
state.PushArray(obj);
|
||||
L.PushArray(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -87,15 +87,16 @@ public static class Utils
|
|||
return 1;
|
||||
}
|
||||
|
||||
public static void PushManagedObject<T>(this Lua state, T obj)
|
||||
[Obsolete("This method does not work as intended and requires more research")]
|
||||
public static void PushManagedObject<T>(this Lua L, T obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var members = type.GetMembers().Where(m => m.MemberType == MemberTypes.Method);
|
||||
state.CreateTable(0, members.Count());
|
||||
L.CreateTable(0, members.Count());
|
||||
foreach (var m in members)
|
||||
{
|
||||
state.PushCFunction(L => (int)type.InvokeMember(m.Name, BindingFlags.InvokeMethod, null, obj, new object[] { L }));
|
||||
state.SetField(-2, m.Name);
|
||||
L.PushCFunction(L => (int)type.InvokeMember(m.Name, BindingFlags.InvokeMethod, null, obj, new object[] { L }));
|
||||
L.SetField(-2, m.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@ namespace Capy64.LuaRuntime.Libraries;
|
|||
|
||||
public class FileSystem : IPlugin
|
||||
{
|
||||
public static string BasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "Capy64");
|
||||
public static string DataPath = Path.Combine(BasePath, "data");
|
||||
public static string DataPath = Path.Combine(Capy64.AppDataPath, "data");
|
||||
|
||||
public FileSystem()
|
||||
{
|
||||
|
@ -451,7 +450,7 @@ public class FileSystem : IPlugin
|
|||
foreach (var attribute in attributes)
|
||||
{
|
||||
L.PushString(attribute.Key);
|
||||
L.PushValue(attribute.Value);
|
||||
Extensions.Utils.PushObject(L, attribute.Value);
|
||||
|
||||
L.SetTable(-3);
|
||||
}
|
||||
|
|
|
@ -2,21 +2,180 @@
|
|||
using KeraLua;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Capy64.LuaRuntime.Libraries;
|
||||
|
||||
#nullable enable
|
||||
public class HTTP : IPlugin
|
||||
{
|
||||
private static IGame _game;
|
||||
private static HttpClient _client;
|
||||
private static long RequestId;
|
||||
|
||||
private readonly LuaRegister[] HttpLib = new LuaRegister[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
name = "request",
|
||||
function = L_Request,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "checkURL",
|
||||
function = L_CheckUrl,
|
||||
},
|
||||
new(),
|
||||
};
|
||||
public HTTP(IGame game)
|
||||
{
|
||||
_game = game;
|
||||
RequestId = 0;
|
||||
_client = new();
|
||||
_client.DefaultRequestHeaders.Add("User-Agent", $"Capy64/{Capy64.Version}");
|
||||
}
|
||||
|
||||
public void LuaInit(Lua state)
|
||||
public void LuaInit(Lua L)
|
||||
{
|
||||
L.RequireF("http", Open, false);
|
||||
}
|
||||
|
||||
private int Open(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
L.NewLib(HttpLib);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static bool TryGetUri(string url, out Uri? uri)
|
||||
{
|
||||
return (Uri.TryCreate(url, UriKind.Absolute, out uri)
|
||||
&& uri?.Scheme == Uri.UriSchemeHttp) || uri?.Scheme == Uri.UriSchemeHttps;
|
||||
}
|
||||
|
||||
private static int L_Request(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
var request = new HttpRequestMessage();
|
||||
|
||||
var url = L.CheckString(1);
|
||||
if (!TryGetUri(url, out Uri? uri) || uri is null)
|
||||
{
|
||||
L.ArgumentError(1, "invalid request url");
|
||||
return 0;
|
||||
}
|
||||
request.RequestUri = uri;
|
||||
|
||||
if (L.IsTable(3)) // headers
|
||||
{
|
||||
L.PushCopy(3);
|
||||
L.PushNil();
|
||||
|
||||
while (L.Next(-2))
|
||||
{
|
||||
L.PushCopy(-2);
|
||||
|
||||
var k = L.CheckString(-1);
|
||||
if (L.IsStringOrNumber(-2))
|
||||
{
|
||||
var v = L.ToString(-2);
|
||||
|
||||
request.Headers.Add(k, v);
|
||||
}
|
||||
else if (L.IsNil(-2))
|
||||
{
|
||||
request.Headers.Remove(k);
|
||||
}
|
||||
else
|
||||
{
|
||||
L.ArgumentError(3, "string, number or nil expected, got " + L.TypeName(L.Type(-2)) + " in field " + k);
|
||||
}
|
||||
|
||||
L.Pop(2);
|
||||
}
|
||||
|
||||
L.Pop(1);
|
||||
}
|
||||
|
||||
var options = new Dictionary<string, object>
|
||||
{
|
||||
["binary"] = false,
|
||||
};
|
||||
|
||||
if (L.IsTable(4)) // other options?
|
||||
{
|
||||
L.PushCopy(4);
|
||||
L.PushNil();
|
||||
|
||||
while (L.Next(-2))
|
||||
{
|
||||
L.PushCopy(-2);
|
||||
var k = L.CheckString(-1);
|
||||
|
||||
switch (k)
|
||||
{
|
||||
case "method":
|
||||
options["method"] = L.CheckString(-2);
|
||||
break;
|
||||
case "binary":
|
||||
options["binary"] = L.IsBoolean(-2) ? L.ToBoolean(-2) : false;
|
||||
break;
|
||||
}
|
||||
|
||||
L.Pop(2);
|
||||
}
|
||||
|
||||
L.Pop(1);
|
||||
}
|
||||
|
||||
if (!L.IsNoneOrNil(2))
|
||||
{
|
||||
if ((bool)options["binary"])
|
||||
{
|
||||
request.Content = new ByteArrayContent(L.CheckBuffer(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Content = new StringContent(L.CheckString(2));
|
||||
}
|
||||
}
|
||||
|
||||
request.Method = options.TryGetValue("method", out var value)
|
||||
? new HttpMethod((string)value)
|
||||
: request.Content is not null ? HttpMethod.Post : HttpMethod.Get;
|
||||
|
||||
var requestId = RequestId++;
|
||||
|
||||
var reqTask = _client.SendAsync(request);
|
||||
reqTask.ContinueWith(async (task) =>
|
||||
{
|
||||
var response = await task;
|
||||
object content;
|
||||
if ((bool)options["binary"])
|
||||
content = await response.Content.ReadAsByteArrayAsync();
|
||||
else
|
||||
content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
_game.LuaRuntime.PushEvent("http_response", requestId, content, (int)response.StatusCode);
|
||||
else
|
||||
_game.LuaRuntime.PushEvent("http_failure", requestId, content, (int)response.StatusCode, response.ReasonPhrase);
|
||||
});
|
||||
|
||||
L.PushInteger(requestId);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int L_CheckUrl(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var url = L.CheckString(1);
|
||||
|
||||
var isValid = TryGetUri(url, out _);
|
||||
|
||||
L.PushBoolean(isValid);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public class OS : IPlugin
|
|||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
L.PushString(Capy64.Version);
|
||||
L.PushString("Capy64 " + Capy64.Version);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public class Runtime
|
|||
{
|
||||
foreach (var par in ev.Parameters)
|
||||
{
|
||||
Thread.PushValue(par);
|
||||
Thread.PushObject(par);
|
||||
}
|
||||
}
|
||||
return (ev.Parameters?.Length ?? 0) + 1;
|
||||
|
|
Loading…
Reference in a new issue