Skip to content

Commit e8cf5ed

Browse files
authored
Add Script Engine
1 parent 6a6a783 commit e8cf5ed

File tree

1 file changed

+239
-0
lines changed

1 file changed

+239
-0
lines changed

HashifyNETCLI/Core/ScriptEngine.cs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// *
2+
// *****************************************************************************
3+
// *
4+
// * Copyright (c) 2025 Deskasoft International
5+
// *
6+
// * Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// * of this software and associated documentation files (the ""Software""), to deal
8+
// * in the Software without restriction, including without limitation the rights
9+
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// * copies of the Software, and to permit persons to whom the Software is
11+
// * furnished to do so, subject to the following conditions:
12+
// *
13+
// * The above copyright notice and this permission notice shall be included in all
14+
// * copies or substantial portions of the Software.
15+
// *
16+
// * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// * SOFTWARE.
23+
// *
24+
// *
25+
// * Please refer to LICENSE file.
26+
// *
27+
// ******************************************************************************
28+
// *
29+
30+
using HashifyNet;
31+
using NLua;
32+
using NLua.Exceptions;
33+
using System.Reflection;
34+
using System.Text;
35+
36+
namespace HashifyNETCLI
37+
{
38+
public sealed class ScriptEngine : IDisposable
39+
{
40+
private bool disposedValue;
41+
42+
private Lua _luaState;
43+
public ScriptEngine()
44+
{
45+
_luaState = new Lua();
46+
_luaState.State.Encoding = Encoding.UTF8;
47+
_luaState.LoadCLRPackage();
48+
_luaState.DoString("import ('HashifyNet', 'HashifyNet')");
49+
_luaState.DoString("import ('HashifyNETCLI', 'HashifyNETCLI')");
50+
_luaState.DoString("import ('System', 'System')");
51+
_luaState.DoString("import ('System.Core', 'System.Linq')");
52+
53+
PushLuaHelpers(_luaState, typeof(ScriptHelpers));
54+
PushLuaHelpers(_luaState, typeof(System.IO.Directory));
55+
PushLuaHelpers(_luaState, typeof(System.IO.File));
56+
PushLuaHelpers(_luaState, typeof(System.String));
57+
}
58+
59+
private static void PushLuaHelpers(Lua state, Type staticType)
60+
{
61+
MethodInfo[] mis = staticType.GetMethods(BindingFlags.Public | BindingFlags.Static);
62+
63+
if (mis.Length < 1)
64+
{
65+
Logger.Warning($"No public static method available in '{staticType.FullName}' to bind into Lua.");
66+
return;
67+
}
68+
69+
foreach (MethodInfo mi in mis)
70+
{
71+
state.RegisterFunction(mi.Name, null, mi);
72+
}
73+
}
74+
75+
private static void CleanAndRegister(Lua state, string name, object target, MethodInfo method)
76+
{
77+
if (state[name] != null)
78+
{
79+
if (state[name] is IDisposable disposable)
80+
{
81+
disposable.Dispose();
82+
}
83+
84+
state[name] = null;
85+
}
86+
87+
state.RegisterFunction(name, target, method);
88+
}
89+
90+
public static void PushLuaVariables(Lua state, object instance)
91+
{
92+
Type type = instance.GetType();
93+
94+
PropertyInfo[] pis = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
95+
foreach (PropertyInfo pi in pis)
96+
{
97+
if (!pi.CanRead) continue;
98+
state[pi.Name] = pi.GetValue(instance);
99+
}
100+
101+
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
102+
.Where(m => !m.IsSpecialName);
103+
104+
var methodGroups = methods.GroupBy(m => m.Name);
105+
106+
foreach (var group in methodGroups)
107+
{
108+
if (group.Count() == 1)
109+
{
110+
var singleMethod = group.Single();
111+
CleanAndRegister(state, group.Key, instance, singleMethod);
112+
}
113+
else
114+
{
115+
MethodInfo primaryMethod;
116+
117+
var noParamMethod = group.FirstOrDefault(m => m.GetParameters().Length == 0);
118+
119+
if (noParamMethod != null)
120+
{
121+
primaryMethod = noParamMethod;
122+
}
123+
else
124+
{
125+
primaryMethod = group.OrderBy(m => m.GetParameters().Length)
126+
.ThenBy(m => m.ToString())
127+
.First();
128+
}
129+
130+
CleanAndRegister(state, group.Key, instance, primaryMethod);
131+
132+
foreach (var overloadedMethod in group.Where(m => m != primaryMethod))
133+
{
134+
string suffix = string.Join("_", overloadedMethod.GetParameters().Select(p => p.ParameterType.Name));
135+
CleanAndRegister(state, $"{group.Key}_{suffix}", instance, overloadedMethod);
136+
}
137+
}
138+
}
139+
}
140+
141+
private static string PrepareScript(string script)
142+
{
143+
return "return " + script;
144+
}
145+
146+
private object[] ExecuteScript(string script)
147+
{
148+
try
149+
{
150+
return _luaState.DoString(script);
151+
}
152+
catch (LuaScriptException ex)
153+
{
154+
if (ex.IsNetException && ex.InnerException != null)
155+
{
156+
throw ex.InnerException;
157+
}
158+
159+
throw;
160+
}
161+
}
162+
163+
public object InputScript(string script)
164+
{
165+
script = PrepareScript(script);
166+
167+
object[] res = ExecuteScript(script);
168+
if (res == null || res.Length < 1)
169+
{
170+
Logger.Error("Input script did not return any value.");
171+
return null!;
172+
}
173+
return res[0]!;
174+
}
175+
176+
public object FinalizeInputScript(string finalizer, object input)
177+
{
178+
finalizer = PrepareScript(finalizer);
179+
180+
_luaState["Input"] = input;
181+
object[] res = ExecuteScript(finalizer);
182+
183+
if (res == null || res.Length < 1)
184+
{
185+
Logger.Error("Input finalizer script did not return any value.");
186+
return null!;
187+
}
188+
189+
return res[0]!;
190+
}
191+
192+
public void OutputScript(string script, object result, string algorithm)
193+
{
194+
_luaState["Result"] = result;
195+
_luaState["Algorithm"] = algorithm;
196+
ExecuteScript(script);
197+
}
198+
199+
public object FinalizeOutputScript(string finalizer, IHashValue value)
200+
{
201+
finalizer = PrepareScript(finalizer);
202+
203+
PushLuaVariables(_luaState, value);
204+
object[] res = ExecuteScript(finalizer);
205+
206+
if (res == null || res.Length < 1)
207+
{
208+
Logger.Error("Output finalizer script did not return any value.");
209+
return null!;
210+
}
211+
212+
return res[0]!;
213+
}
214+
215+
private void Dispose(bool disposing)
216+
{
217+
if (!disposedValue)
218+
{
219+
_luaState.Dispose();
220+
_luaState = null!;
221+
222+
disposedValue = true;
223+
}
224+
}
225+
226+
~ScriptEngine()
227+
{
228+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
229+
Dispose(disposing: false);
230+
}
231+
232+
public void Dispose()
233+
{
234+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
235+
Dispose(disposing: true);
236+
GC.SuppressFinalize(this);
237+
}
238+
}
239+
}

0 commit comments

Comments
 (0)