2
0
Эх сурвалжийг харах

Merge pull request #16 from AnnulusGames/os-library

Add: os library
Annulus Games 1 жил өмнө
parent
commit
b3eb8dc763

+ 27 - 1
src/Lua/Standard/OpenLibExtensions.cs

@@ -3,6 +3,7 @@ using Lua.Standard.Coroutines;
 using Lua.Standard.IO;
 using Lua.Standard.Mathematics;
 using Lua.Standard.Modules;
+using Lua.Standard.OperatingSystem;
 using Lua.Standard.Table;
 
 namespace Lua.Standard;
@@ -67,7 +68,7 @@ public static class OpenLibExtensions
     static readonly LuaFunction[] tableFunctions = [
         PackFunction.Instance,
         UnpackFunction.Instance,
-        RemoveFunction.Instance,
+        Table.RemoveFunction.Instance,
         ConcatFunction.Instance,
         InsertFunction.Instance,
         SortFunction.Instance,
@@ -84,6 +85,20 @@ public static class OpenLibExtensions
         IO.TypeFunction.Instance,
     ];
 
+    static readonly LuaFunction[] osFunctions = [
+        ClockFunction.Instance,
+        DateFunction.Instance,
+        DiffTimeFunction.Instance,
+        ExecuteFunction.Instance,
+        ExitFunction.Instance,
+        GetEnvFunction.Instance,
+        OperatingSystem.RemoveFunction.Instance,
+        RenameFunction.Instance,
+        SetLocaleFunction.Instance,
+        TimeFunction.Instance,
+        TmpNameFunction.Instance,
+    ];
+
     public static void OpenBasicLibrary(this LuaState state)
     {
         // basic
@@ -156,4 +171,15 @@ public static class OpenLibExtensions
 
         state.Environment["io"] = io;
     }
+
+    public static void OpenOperatingSystemLibrary(this LuaState state)
+    {
+        var os = new LuaTable(0, osFunctions.Length);
+        foreach (var func in osFunctions)
+        {
+            os[func.Name] = func;
+        }
+
+        state.Environment["os"] = os;
+    }
 }

+ 16 - 0
src/Lua/Standard/OperatingSystem/ClockFunction.cs

@@ -0,0 +1,16 @@
+
+using System.Diagnostics;
+
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class ClockFunction : LuaFunction
+{
+    public override string Name => "clock";
+    public static readonly ClockFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        buffer.Span[0] = DateTimeHelper.GetUnixTime(DateTime.UtcNow, Process.GetCurrentProcess().StartTime);
+        return new(1);
+    }
+}

+ 195 - 0
src/Lua/Standard/OperatingSystem/DateFunction.cs

@@ -0,0 +1,195 @@
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class DateFunction : LuaFunction
+{
+    public override string Name => "date";
+    public static readonly DateFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var format = context.HasArgument(0)
+            ? context.GetArgument<string>(0).AsSpan()
+            : "%c".AsSpan();
+
+        DateTime now;
+        if (context.HasArgument(1))
+        {
+            var time = context.GetArgument<double>(1);
+            now = DateTimeHelper.FromUnixTime(time);
+        }
+        else
+        {
+            now = DateTime.UtcNow;
+        }
+
+        var isDst = false;
+        if (format[0] == '!')
+        {
+            format = format[1..];
+        }
+        else
+        {
+            now = TimeZoneInfo.ConvertTimeFromUtc(now, TimeZoneInfo.Local);
+            isDst = now.IsDaylightSavingTime();
+        }
+
+        if (format == "*t")
+        {
+            var table = new LuaTable();
+            
+            table["year"] = now.Year;
+            table["month"] = now.Month;
+            table["day"] = now.Day;
+            table["hour"] = now.Hour;
+            table["min"] = now.Minute;
+            table["sec"] = now.Second;
+            table["wday"] = ((int)now.DayOfWeek) + 1;
+            table["yday"] = now.DayOfYear;
+            table["isdst"] = isDst;
+
+            buffer.Span[0] = table;
+        }
+        else
+        {
+            buffer.Span[0] = StrFTime(context.State, format, now);
+        }
+
+        return new(1);
+    }
+
+    static string StrFTime(LuaState state, ReadOnlySpan<char> format, DateTime d)
+    {
+        // reference: http://www.cplusplus.com/reference/ctime/strftime/
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        static string? STANDARD_PATTERNS(char c)
+        {
+            return c switch
+            {
+                'a' => "ddd",
+                'A' => "dddd",
+                'b' => "MMM",
+                'B' => "MMMM",
+                'c' => "f",
+                'd' => "dd",
+                'D' => "MM/dd/yy",
+                'F' => "yyyy-MM-dd",
+                'g' => "yy",
+                'G' => "yyyy",
+                'h' => "MMM",
+                'H' => "HH",
+                'I' => "hh",
+                'm' => "MM",
+                'M' => "mm",
+                'p' => "tt",
+                'r' => "h:mm:ss tt",
+                'R' => "HH:mm",
+                'S' => "ss",
+                'T' => "HH:mm:ss",
+                'y' => "yy",
+                'Y' => "yyyy",
+                'x' => "d",
+                'X' => "T",
+                'z' => "zzz",
+                'Z' => "zzz",
+                _ => null,
+            };
+        }
+
+        var builder = new ValueStringBuilder();
+
+        bool isEscapeSequence = false;
+
+        for (int i = 0; i < format.Length; i++)
+        {
+            char c = format[i];
+
+            if (c == '%')
+            {
+                if (isEscapeSequence)
+                {
+                    builder.Append('%');
+                    isEscapeSequence = false;
+                }
+
+                continue;
+            }
+
+            if (!isEscapeSequence)
+            {
+                builder.Append(c);
+                continue;
+            }
+
+            if (c == 'O' || c == 'E')
+            {
+                continue; // no modifiers
+            }
+
+            isEscapeSequence = false;
+
+            var pattern = STANDARD_PATTERNS(c);
+            if (pattern != null)
+            {
+                builder.Append(d.ToString(pattern));
+            }
+            else if (c == 'e')
+            {
+                var s = d.ToString("%d");
+                builder.Append(s.Length < 2 ? $" {s}" : s);
+            }
+            else if (c == 'n')
+            {
+                builder.Append('\n');
+            }
+            else if (c == 't')
+            {
+                builder.Append('\t');
+            }
+            else if (c == 'C')
+            {
+                // TODO: reduce allocation
+                builder.Append((d.Year / 100).ToString());
+            }
+            else if (c == 'j')
+            {
+                builder.Append(d.DayOfYear.ToString("000"));
+            }
+            else if (c == 'u')
+            {
+                int weekDay = (int)d.DayOfWeek;
+                if (weekDay == 0) weekDay = 7;
+                builder.Append(weekDay.ToString());
+            }
+            else if (c == 'w')
+            {
+                int weekDay = (int)d.DayOfWeek;
+                builder.Append(weekDay.ToString());
+            }
+            else if (c == 'U')
+            {
+                // Week number with the first Sunday as the first day of week one (00-53)
+                builder.Append("??");
+            }
+            else if (c == 'V')
+            {
+                // ISO 8601 week number (00-53)
+                builder.Append("??");
+            }
+            else if (c == 'W')
+            {
+                // Week number with the first Monday as the first day of week one (00-53)
+                builder.Append("??");
+            }
+            else
+            {
+                throw new LuaRuntimeException(state.GetTraceback(), $"bad argument #1 to 'date' (invalid conversion specifier '{format}')");
+            }
+        }
+
+        return builder.ToString();
+    }
+}

+ 62 - 0
src/Lua/Standard/OperatingSystem/DateTimeHelper.cs

@@ -0,0 +1,62 @@
+using System.Runtime.CompilerServices;
+using Lua.Runtime;
+
+namespace Lua.Standard.OperatingSystem;
+
+internal static class DateTimeHelper
+{
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static double GetUnixTime(DateTime dateTime)
+    {
+        return GetUnixTime(dateTime, DateTime.UnixEpoch);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static double GetUnixTime(DateTime dateTime, DateTime epoch)
+    {
+        var time = (dateTime - epoch).TotalSeconds;
+        if (time < 0.0) return 0;
+        return time;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static DateTime FromUnixTime(double unixTime)
+    {
+        var ts = TimeSpan.FromSeconds(unixTime);
+        return DateTime.UnixEpoch + ts;
+    }
+
+    public static DateTime ParseTimeTable(LuaState state, LuaTable table)
+    {
+        static int GetTimeField(LuaState state, LuaTable table, string key, bool required = true, int defaultValue = 0)
+        {
+            if (!table.TryGetValue(key, out var value))
+            {
+                if (required)
+                {
+                    throw new LuaRuntimeException(state.GetTraceback(), $"field '{key}' missing in date table");
+                }
+                else
+                {
+                    return defaultValue;
+                }
+            }
+
+            if (value.TryGetNumber(out var d) && MathEx.IsInteger(d))
+            {
+                return (int)d;
+            }
+
+            throw new LuaRuntimeException(state.GetTraceback(), $"field '{key}' is not an integer");
+        }
+
+        var day = GetTimeField(state, table, "day");
+        var month = GetTimeField(state, table, "month");
+        var year = GetTimeField(state, table, "year");
+        var sec = GetTimeField(state, table, "sec", false, 0);
+        var min = GetTimeField(state, table, "min", false, 0);
+        var hour = GetTimeField(state, table, "hour", false, 12);
+
+        return new DateTime(year, month, day, hour, min, sec);
+    }
+}

+ 18 - 0
src/Lua/Standard/OperatingSystem/DiffTimeFunction.cs

@@ -0,0 +1,18 @@
+
+using System.Diagnostics;
+
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class DiffTimeFunction : LuaFunction
+{
+    public override string Name => "difftime";
+    public static readonly DiffTimeFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var t2 = context.GetArgument<double>(0);
+        var t1 = context.GetArgument<double>(1);
+        buffer.Span[0] = t2 - t1;
+        return new(1);
+    }
+}

+ 23 - 0
src/Lua/Standard/OperatingSystem/ExecuteFunction.cs

@@ -0,0 +1,23 @@
+
+namespace Lua.Standard.OperatingSystem;
+
+// os.execute(command) is not supported (always return nil)
+
+public sealed class ExecuteFunction : LuaFunction
+{
+    public override string Name => "execute";
+    public static readonly SetLocaleFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        if (context.HasArgument(0))
+        {
+            throw new NotSupportedException("os.execute(command) is not supported");
+        }
+        else
+        {
+            buffer.Span[0] = false;
+            return new(1);
+        }
+    }
+}

+ 44 - 0
src/Lua/Standard/OperatingSystem/ExitFunction.cs

@@ -0,0 +1,44 @@
+
+using Lua.Runtime;
+
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class ExitFunction : LuaFunction
+{
+    public override string Name => "exit";
+    public static readonly ExitFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        // Ignore 'close' parameter
+
+        if (context.HasArgument(0))
+        {
+            var code = context.Arguments[0];
+
+            if (code.TryRead<bool>(out var b))
+            {
+                Environment.Exit(b ? 0 : 1);
+            }
+            else if (code.TryGetNumber(out var d))
+            {
+                if (!MathEx.IsInteger(d))
+                {
+                    throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #1 to 'exit' (number has no integer representation)");
+                }
+
+                Environment.Exit((int)d);
+            }
+            else
+            {
+                LuaRuntimeException.BadArgument(context.State.GetTraceback(), 1, Name, LuaValueType.Nil.ToString(), code.Type.ToString());
+            }
+        }
+        else
+        {
+            Environment.Exit(0);
+        }
+
+        return new(0);
+    }
+}

+ 15 - 0
src/Lua/Standard/OperatingSystem/GetEnvFuntion.cs

@@ -0,0 +1,15 @@
+
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class GetEnvFunction : LuaFunction
+{
+    public override string Name => "getenv";
+    public static readonly GetEnvFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var variable = context.GetArgument<string>(0);
+        buffer.Span[0] = Environment.GetEnvironmentVariable(variable) ?? LuaValue.Nil;
+        return new(1);
+    }
+}

+ 25 - 0
src/Lua/Standard/OperatingSystem/RemoveFunction.cs

@@ -0,0 +1,25 @@
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class RemoveFunction : LuaFunction
+{
+    public override string Name => "remove";
+    public static readonly RemoveFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var fileName = context.GetArgument<string>(0);
+        try
+        {
+            File.Delete(fileName);
+            buffer.Span[0] = true;
+            return new(1);
+        }
+        catch(IOException ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return new(3);
+        }
+    }
+}

+ 26 - 0
src/Lua/Standard/OperatingSystem/RenameFunction.cs

@@ -0,0 +1,26 @@
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class RenameFunction : LuaFunction
+{
+    public override string Name => "rename";
+    public static readonly RenameFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var oldName = context.GetArgument<string>(0);
+        var newName = context.GetArgument<string>(1);
+        try
+        {
+            File.Move(oldName, newName);
+            buffer.Span[0] = true;
+            return new(1);
+        }
+        catch(IOException ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return new(3);
+        }
+    }
+}

+ 16 - 0
src/Lua/Standard/OperatingSystem/SetLocaleFunction.cs

@@ -0,0 +1,16 @@
+
+namespace Lua.Standard.OperatingSystem;
+
+// os.setlocale is not supported (always return nil)
+
+public sealed class SetLocaleFunction : LuaFunction
+{
+    public override string Name => "setlocale";
+    public static readonly SetLocaleFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        buffer.Span[0] = LuaValue.Nil;
+        return new(1);
+    }
+}

+ 23 - 0
src/Lua/Standard/OperatingSystem/TimeFunction.cs

@@ -0,0 +1,23 @@
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class TimeFunction : LuaFunction
+{
+    public override string Name => "time";
+    public static readonly TimeFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        if (context.HasArgument(0))
+        {
+            var table = context.GetArgument<LuaTable>(0);
+            var date = DateTimeHelper.ParseTimeTable(context.State, table);
+            buffer.Span[0] = DateTimeHelper.GetUnixTime(date);
+            return new(1);
+        }
+        else
+        {
+            buffer.Span[0] = DateTimeHelper.GetUnixTime(DateTime.UtcNow);
+            return new(1);
+        }
+    }
+}

+ 13 - 0
src/Lua/Standard/OperatingSystem/TmpNameFunction.cs

@@ -0,0 +1,13 @@
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class TmpNameFunction : LuaFunction
+{
+    public override string Name => "tmpname";
+    public static readonly TmpNameFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        buffer.Span[0] = Path.GetTempFileName();
+        return new(1);
+    }
+}