Files
2026-05-08 14:54:47 +02:00

255 lines
9.3 KiB
C#
Executable File

using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
namespace JCIL.Java.Class;
public sealed class TypeLoader
{
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, string> _files =
new(ReadOnlyMemoryCharComparer.Instance);
private readonly ConcurrentDictionary<ReadOnlyMemory<char>, Class> _classes =
new(ReadOnlyMemoryCharComparer.Instance);
public Class Void => field ??= GetClass("V".AsMemory());
public Class Char => field ??= GetClass("C".AsMemory());
public Class Byte => field ??= GetClass("B".AsMemory());
public Class Short => field ??= GetClass("S".AsMemory());
public Class Int => field ??= GetClass("I".AsMemory());
public Class Long => field ??= GetClass("J".AsMemory());
public Class Float => field ??= GetClass("F".AsMemory());
public Class Double => field ??= GetClass("D".AsMemory());
public Class Boolean => field ??= GetClass("Z".AsMemory());
public Class Object => field ??= LoadClass("java/lang/Object".AsMemory());
public void AddPath(string path)
{
var basePath = path.AsSpan().TrimEnd(Path.DirectorySeparatorChar);
foreach (var file in Directory.EnumerateFiles(path, "*.class", SearchOption.AllDirectories))
{
var name = file[(basePath.Length + 1)..^6];
_files.AddOrUpdate(name.AsMemory(), file, (_, v) => v);
}
}
public Class GetClass(ReadOnlyMemory<char> className)
{
return _classes.TryGetValue(className, out var existing) ? existing : GetClass(className, out _);
}
public Class GetClass(ReadOnlyMemory<char> className, out ReadOnlyMemory<char> rest)
{
var firstChar = className.Span[0];
rest = className[1..];
switch (firstChar)
{
// Init intrinsics
case 'Z':
return _classes.GetOrAdd("Z".AsMemory(), _ => new Class(this)
{
Name = "boolean".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Boolean)
});
case 'B':
return _classes.GetOrAdd("B".AsMemory(), _ => new Class(this)
{
Name = "byte".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Byte)
});
case 'C':
return _classes.GetOrAdd("C".AsMemory(), _ => new Class(this)
{
Name = "char".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Char)
});
case 'S':
return _classes.GetOrAdd("S".AsMemory(), _ => new Class(this)
{
Name = "short".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Short)
});
case 'I':
return _classes.GetOrAdd("I".AsMemory(), _ => new Class(this)
{
Name = "int".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Int)
});
case 'J':
return _classes.GetOrAdd("J".AsMemory(), _ => new Class(this)
{
Name = "long".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Long)
});
case 'F':
return _classes.GetOrAdd("F".AsMemory(), _ => new Class(this)
{
Name = "float".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Float)
});
case 'D':
return _classes.GetOrAdd("D".AsMemory(), _ => new Class(this)
{
Name = "double".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Double)
});
case 'V':
return _classes.GetOrAdd("V".AsMemory(), _ => new Class(this)
{
Name = "void".AsMemory(),
Namespace = "java/lang".AsMemory(),
SpecialClassMetadata = new IntrinsicMetadata(IntrinsicType.Void)
});
case 'L' when className.Span.IndexOf('<') is var genericsStart and > 0:
{
var theName = className.Span;
var semicolon = theName.IndexOf(';');
if (semicolon < genericsStart)
{
className = className[..(semicolon + 1)];
rest = rest[semicolon..];
return GetClass(className);
}
className = rest[..(genericsStart - 1)];
rest = rest[genericsStart..];
var loop = true;
var generics = new List<Class>();
while (loop)
{
switch (rest.Span[0])
{
case '>': loop = false; break;
case '*':
generics.Add(Object);
rest = rest[1..];
break;
default: generics.Add(GetClass(rest, out rest)); break;
}
}
rest = rest[2..];
return LoadClass(className).WithGenerics(generics);
}
case 'L':
{
className = rest[..rest.Span.IndexOf(';')];
rest = rest[(className.Length + 1)..];
return LoadClass(className);
}
case 'T':
{
className = rest[..rest.Span.IndexOf(';')];
rest = rest[(className.Length + 1)..];
// TODO Actually load a type from context
return Object;
}
case '[':
{
var arrayName = rest;
var arrayElem = GetClass(arrayName, out rest);
arrayName = arrayName[..^rest.Length];
arrayName = className[..(arrayName.Length + 1)];
return _classes.GetOrAdd(arrayName, _ =>
{
var arrClass = new Class(this)
{
Name = arrayName,
SuperClass = Object,
SpecialClassMetadata = new ArrayMetadata(arrayElem),
};
return arrClass;
});
}
case '(':
{
var args = new List<Class>();
while (rest.Span[0] != ')') args.Add(GetClass(rest, out rest));
rest = rest[1..];
var retClass = GetClass(rest, out rest);
className = className[..^rest.Length];
return _classes.GetOrAdd(className, _ => new Class(this)
{
SpecialClassMetadata = new MethodSignature(retClass, args)
});
}
// TODO Handle this somehow
case '+': return GetClass(rest, out rest);
case '-': return GetClass(rest, out rest);
default:
{
rest = default;
return LoadClass(className);
}
}
}
public Class LoadClass(ReadOnlyMemory<char> className)
{
var mustLoad = false;
var javaClass = _classes.GetOrAdd(className, _ =>
{
mustLoad = true;
var javaClass = new Class(this);
Monitor.Enter(javaClass);
return javaClass;
});
if (mustLoad)
{
if (!_files.TryGetValue(className, out var classFile))
throw new FileNotFoundException($"{className}.class was not found");
// Console.WriteLine($"Loading class {className}");
using var file = File.OpenRead(classFile);
using var reader = new BufferedStream(file);
javaClass.Read(reader);
Monitor.Exit(javaClass);
}
else
{
lock (javaClass)
{
// Wait for it to be loaded
}
}
return javaClass;
}
private static ReadOnlyMemory<char> SliceFromSpan(ReadOnlyMemory<char> memory, ReadOnlySpan<char> span)
{
var mem = memory.Span;
var start = Unsafe.ByteOffset(in mem[0], in span[0]) / sizeof(char);
return memory.Slice((int)start, span.Length);
}
}
internal class ReadOnlyMemoryCharComparer : IEqualityComparer<ReadOnlyMemory<char>>
{
public static readonly ReadOnlyMemoryCharComparer Instance = new();
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y)
=> x.Span.SequenceEqual(y.Span);
public int GetHashCode(ReadOnlyMemory<char> obj)
=> string.GetHashCode(obj.Span);
}