Skip to content

Commit eb88291

Browse files
Updated Interpreter. I've been adding a lot. It's basically done
1 parent 5aab8dd commit eb88291

4 files changed

Lines changed: 119 additions & 34 deletions

File tree

EZCode/Interpreter.cs

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Linq;
2-
using System.Reflection;
1+
using System.Reflection;
2+
using System.Runtime.Loader;
33
using static EZCodeLanguage.Parser;
44

55
namespace EZCodeLanguage
@@ -103,9 +103,9 @@ public Interpreter(Parser parser, Debug.Breakpoint[]? breakpoints = null)
103103
private object? returned = null;
104104
private DataType? Returning = null;
105105
private int StackNumber = 0;
106+
internal bool hasexited = false;
107+
public CustomAssemblyLoadContext[] LoadedAssemblies = [];
106108
public Stack<string> StackTrace { get; private set; }
107-
internal record Library(string name, string[] files);
108-
internal List<Library> DllLibraries { get; set; } = [];
109109
public Exception[] Errors { get; private set; } = [];
110110
public Var[] Vars { get; set; } = [];
111111
public Method[] Methods { get; set; } = [];
@@ -123,6 +123,12 @@ public void Interperate(LineWithTokens[] LineTokens)
123123
if (Methods.Any(x => x.Name.ToLower() == "start") && !StartMethodEntry)
124124
{
125125
StartMethodEntry = true;
126+
LineWithTokens[] lines_with_include = LineTokens.Where(x => x.Tokens.Length > 1).Where(x => x.Tokens[0].Type == TokenType.Include).ToArray();
127+
LineWithTokens[] lines_with_exclude = LineTokens.Where(x => x.Tokens.Length > 1).Where(x => x.Tokens[0].Type == TokenType.Exclude).ToArray();
128+
if (lines_with_include.Length > 0)
129+
Interperate(lines_with_include);
130+
if (lines_with_exclude.Length > 0)
131+
Interperate(lines_with_exclude);
126132
LineWithTokens[] lines_with_global = LineTokens.Where(x => x.Tokens.Length > 1).Where(x => x.Tokens[0].Type == TokenType.Global).ToArray();
127133
if (lines_with_global.Length > 0)
128134
Interperate(lines_with_global);
@@ -131,8 +137,9 @@ public void Interperate(LineWithTokens[] LineTokens)
131137
}
132138
else
133139
{
134-
foreach (LineWithTokens line in LineTokens)
140+
foreach (LineWithTokens line in LineTokens)
135141
{
142+
if (hasexited) break;
136143
if (line.Tokens.Length == 0 || (line.Tokens.Length == 1 && line.Tokens[0].Value is Class or Method))
137144
continue;
138145

@@ -158,6 +165,18 @@ public void Interperate(LineWithTokens[] LineTokens)
158165
}
159166
}
160167

168+
// Unload each assembly
169+
foreach (var assembly in LoadedAssemblies)
170+
{
171+
assembly.Unload();
172+
}
173+
// Let the garbage collector collect to ensure assemblies are fully unloaded
174+
for (int i = 0; i < 3; i++)
175+
{
176+
GC.Collect();
177+
GC.WaitForPendingFinalizers();
178+
}
179+
// Exclude every package
161180
Package.RemoveAllPackagesFromExecutionDirectory(AppDomain.CurrentDomain.BaseDirectory);
162181
}
163182
internal object? SingleLine(LineWithTokens line)
@@ -187,29 +206,24 @@ public void Interperate(LineWithTokens[] LineTokens)
187206
string combined_packages = string.Join(" ", line.Tokens.Skip(1).Select(x => x.StringValue));
188207
string[] packages = combined_packages.Split(",").Select(x=>x.Trim()).ToArray();
189208
Project[] projects = new Project[packages.Length];
209+
bool include = FirstToken.StringValue == "include";
190210

191211
for (int i = 0; i < packages.Length; i++)
192212
projects[i] = Package.GetPackageAsProject(packages[i]);
193213

194-
if (projects.Any(x => !string.IsNullOrEmpty(x.LibraryDirectory)))
214+
if (projects.Any(x => !string.IsNullOrEmpty(x.Configuration?.LibraryDirectory)))
195215
{
196216
string destination = AppDomain.CurrentDomain.BaseDirectory;
197217
foreach (var project in projects)
198218
{
199-
if (FirstToken.StringValue == "include")
200-
{
201-
Package.AddPackageToExecutionDirectory(project, destination, out var files);
202-
DllLibraries.Add(new Library(project.Name, files));
203-
}
219+
if (include)
220+
Package.AddPackageToExecutionDirectory(project, destination);
204221
else
205-
{
206-
var library = DllLibraries.FirstOrDefault(x => x.name == project.Name);
207-
Package.RemovePackageFromExecutionDirectory(library.files);
208-
}
222+
Package.RemovePackageFromExecutionDirectory(project, destination);
209223
}
210224
}
211225

212-
if (FirstToken.StringValue == "include")
226+
if (include)
213227
{
214228
parser = Package.ReturnParserWithPackages(parser, packages);
215229
Methods = [.. Methods, .. parser.Methods];
@@ -429,7 +443,10 @@ public void Interperate(LineWithTokens[] LineTokens)
429443
break;
430444

431445
case IdentType.Method:
432-
Method method = (type is Method m) ? new Method(m.Name, m.Line, m.Settings, m.Lines.Select(x => new LineWithTokens(x.Tokens.Select(y => new Token(y.Type, y.Value, y.StringValue)).ToArray(), x.Line)).ToArray(), m.Parameters, m.Returns) : null;
446+
Method method = (type is Method m) ?
447+
new Method(m.Name, m.Line, m.Settings, m.Lines.Select(
448+
x => new LineWithTokens(x.Tokens.Select(y => new Token(
449+
y.Type, y.Value, y.StringValue)).ToArray(), x.Line)).ToArray(), m.Parameters, m.Returns)! : new Method();
433450

434451
Method.MethodSettings settings = method.Settings;
435452
bool nocol = (settings & Method.MethodSettings.NoCol) != 0;
@@ -571,6 +588,20 @@ public void Interperate(LineWithTokens[] LineTokens)
571588
throw new Exception($"Error with \"undefined\", Error Message:\"{ex.Message}\"");
572589
}
573590
break;
591+
case TokenType.Throw:
592+
string err_message = "";
593+
try
594+
{
595+
line.Tokens = line.Tokens.Skip(1).Prepend(new Token(TokenType.Return, "return", "return")).ToArray();
596+
err_message = SingleLine(line).ToString();
597+
}
598+
catch (Exception ex)
599+
{
600+
throw new Exception($"Error with \"throw\", Error Message:\"{ex.Message}\"");
601+
}
602+
if (err_message != "")
603+
throw new Exception(err_message);
604+
break;
574605
case TokenType.Return:
575606
try
576607
{
@@ -797,6 +828,7 @@ public void Interperate(LineWithTokens[] LineTokens)
797828
{
798829
result = SingleLine(new LineWithTokens(line));
799830

831+
if (hasexited) break;
800832
if (yielded)
801833
{
802834
yielded = false;
@@ -930,6 +962,7 @@ private IdentType IsType(string token, out object? type)
930962
{
931963
result = SingleLine(line);
932964

965+
if (hasexited) break;
933966
if (line.Tokens.Length > 0 && line.Tokens[0].Type == TokenType.Return || returned != null)
934967
break;
935968
}
@@ -1297,32 +1330,39 @@ public object Reflect(CSharpMethod method)
12971330
method = o[0] as CSharpMethod;
12981331
}
12991332

1300-
return InvokeMethod(method.Path, method.Params != null ? method.Params.Select(x => x).ToArray() : [], EZHelp);
1333+
object val = InvokeMethod(method.Path, method.Params != null ? method.Params.Select(x => x).ToArray() : [], EZHelp, out var assembly);
1334+
LoadedAssemblies = LoadedAssemblies.Append(assembly).Where(x => x != null).Distinct().ToArray();
1335+
1336+
return val;
13011337
}
1302-
public static object? InvokeMethod(string methodPath, object[] parameters, EZHelp e)
1338+
public static object? InvokeMethod(string methodPath, object[] parameters, EZHelp e, out CustomAssemblyLoadContext? assemblyContext)
13031339
{
1340+
assemblyContext = null;
13041341
// Split the method files into type and method name
13051342
string[] pathParts = methodPath.Split('.');
13061343
if (pathParts.Length < 2)
13071344
{
13081345
throw new ArgumentException("Invalid method path");
13091346
}
1310-
13111347
if (pathParts.Length >= 2 && pathParts[1].Equals("dll"))
13121348
{
1313-
// If the method files contains an assembly name, load the assembly
1349+
// If the method path contains an assemblyContext name, load the assemblyContext
13141350
string assemblyPath = pathParts[0] + "." + pathParts[1];
1315-
string subdirectory = Package.LibraryDirName; // Name of the subdirectory is first part of namespace
1316-
string fullAssemblyPath = Path.Combine(subdirectory, assemblyPath); // Combine subdirectory files with assembly name
1317-
Assembly assembly = Assembly.LoadFrom(fullAssemblyPath);
1351+
string subdirectory = pathParts[0]; // Name of the subdirectory is first part of namespace
1352+
string relativeAssemblyPath = Path.Combine(subdirectory, assemblyPath); // Combine subdirectory path with assemblyContext name
1353+
string fullAssemblyPath = Path.GetFullPath(relativeAssemblyPath); // Get the absolute path of the assembly
1354+
1355+
// Get Assemblies
1356+
assemblyContext = new CustomAssemblyLoadContext();
1357+
Assembly assembly = assemblyContext.LoadFromAssemblyPath(fullAssemblyPath);
13181358

13191359
// Get the type name
13201360
string typeName = string.Join(".", pathParts.Skip(2).Take(pathParts.Length - 3));
1321-
1361+
13221362
// Get the method name
13231363
string methodName = pathParts.Last();
13241364

1325-
// Get the type from the assembly
1365+
// Get the type from the assemblyContext
13261366
Type type = assembly.GetType(typeName);
13271367
if (type == null)
13281368
{
@@ -1357,7 +1397,7 @@ public object Reflect(CSharpMethod method)
13571397
{
13581398
// Get the type from the full type name
13591399
string typeName = string.Join(".", pathParts.Take(pathParts.Length - 1));
1360-
// Include the assembly information for types in the System namespace
1400+
// Include the assemblyContext information for types in the System namespace
13611401
if (typeName.StartsWith("System."))
13621402
{
13631403
typeName += ", mscorlib";
@@ -1405,5 +1445,17 @@ public object Reflect(CSharpMethod method)
14051445
}
14061446
}
14071447
}
1448+
public class CustomAssemblyLoadContext : AssemblyLoadContext
1449+
{
1450+
public CustomAssemblyLoadContext() : base(isCollectible: true)
1451+
{
1452+
}
1453+
1454+
protected override Assembly Load(AssemblyName assemblyName)
1455+
{
1456+
// Implement if needed: load dependencies, etc.
1457+
return null;
1458+
}
1459+
}
14081460
}
14091461
}

EZCode/Parser.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@ public enum TokenType
419419
Exclude,
420420
Global,
421421
True,
422-
False
422+
False,
423+
Throw
423424
}
424425
public static char[] Delimeters = [' ', '{', '}', '@', ':', ',', '?', '!'];
425426
public string Code { get; set; }
@@ -428,12 +429,23 @@ public enum TokenType
428429
public List<Class> Classes = [];
429430
public List<Method> Methods = [];
430431
public LineWithTokens[] LinesWithTokens = [];
432+
private Parser[] IncludingExcludingPackages = [];
431433
public Parser(string code, string file)
432434
{
433435
Code = code;
434436
FilePath = file;
435437
}
436-
public LineWithTokens[] Parse() => LinesWithTokens = TokenArray(Code, FilePath).Where(x => x.Line.Value.ToString() != "").ToArray();
438+
public LineWithTokens[] Parse()
439+
{
440+
var parse = LinesWithTokens = TokenArray(Code, FilePath).Where(x => x.Line.Value.ToString() != "").ToArray();
441+
442+
foreach (var remove in IncludingExcludingPackages)
443+
{
444+
Package.RemovePackageFromParser(this, remove);
445+
}
446+
447+
return parse;
448+
}
437449
private LineWithTokens[] TokenArray(string code, string file, bool insideClass = false)
438450
{
439451
// Set the file files property
@@ -472,6 +484,14 @@ private LineWithTokens[] TokenArray(string code, string file, bool insideClass =
472484
string combined_packages = string.Join(" ", parts.Skip(1).Select(x => x.ToString()));
473485
string[] packages = combined_packages.Split(",").Select(x => x.Trim()).ToArray();
474486
Parser parser = Package.ReturnParserWithPackages(this, packages);
487+
IncludingExcludingPackages = [.. IncludingExcludingPackages, new Parser(parser.Code, parser.FilePath)
488+
{
489+
Methods = parser.Methods.Select(x=> new Method(x.Name, x.Line, x.Settings, x.Lines, x.Parameters, x.Returns)).ToList(),
490+
Classes = parser.Classes.Select(x=> new Class(x.Name, x.Line, x.Methods, x.Properties, x.WatchFormat, x.Params, x.TypeOf, x.GetTypes, x.Classes, x.Length, x.Aliases)).ToList(),
491+
commentBlock = parser.commentBlock,
492+
IncludingExcludingPackages = parser.IncludingExcludingPackages,
493+
LinesWithTokens = parser.LinesWithTokens
494+
}];
475495
}
476496
if (parts.Length > 1 && parts[0].ToString() == "exclude")
477497
{
@@ -481,6 +501,7 @@ private LineWithTokens[] TokenArray(string code, string file, bool insideClass =
481501
Parser except = Package.ReturnParserWithPackages(new Parser("", ""), packages);
482502
// remove the except parser from the current parser instance
483503
Parser parser = Package.RemovePackageFromParser(this, except);
504+
IncludingExcludingPackages = IncludingExcludingPackages.Except([parser]).ToArray();
484505
}
485506

486507
// loops through each part and creates the token from it
@@ -549,6 +570,7 @@ internal Token SingleToken(object[] parts, int partIndex, string stringPart)
549570
case "null": tokenType = TokenType.Null; parts[partIndex] = ""; break;
550571
case "include": tokenType = TokenType.Include; break;
551572
case "exclude": tokenType = TokenType.Exclude; break;
573+
case "throw": tokenType = TokenType.Throw; break;
552574
}
553575
if (part.StartsWith("//")) tokenType = TokenType.Comment; // If the part starts with '//', it is comment
554576
if (part.StartsWith('@')) tokenType = TokenType.DataType; // If the part starts with '@', it is a datatype
@@ -646,6 +668,17 @@ This keeps loop from skipping index */
646668
return parts.Take(i).ToArray();
647669
}
648670
}
671+
else if (parts[i].ToString() == "!" && parts.Length > i + 1 && parts[i + 1].ToString() == "=")
672+
{
673+
/* if the current part is '!' and the next token is '=', combine the
674+
* parts into a single token. */
675+
676+
// turn the current part into both parts
677+
parts[i] = "!=";
678+
// remove the next part
679+
parts = parts.ToList().Where((item, index) => index != i + 1).ToArray();
680+
681+
}
649682
else if (parts[i].ToString() == "@")
650683
{
651684
// append the next part to the current part

EZCode/Project.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ public class Project
55
public string Name { get; set; }
66
public string[] Files { get; set; }
77
public Config? Configuration { get; set; }
8-
public string? LibraryDirectory { get; set; }
9-
108
public class Config
119
{
10+
public string? LibraryDirectory { get; set; }
1211
public string[]? GlobalPackages { get; set; }
1312
}
1413
}

TestEnv/Code.ezcode

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
include main
1+
include main, time
22

3-
bool match new => regexmatch : hello, hi|hello
4-
print 'match'
3+
method start {
4+
print 'time current'
5+
}

0 commit comments

Comments
 (0)