Skip to content

Commit

Permalink
General improvements and bugfixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Uralstech committed Dec 17, 2024
1 parent 9216608 commit efc6e59
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/EzrSquared.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputTypes>Library</OutputTypes>
<RuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<Platforms>AnyCPU</Platforms>
<TargetFrameworks>net9.0;netstandard2.1</TargetFrameworks>
<LangVersion>13</LangVersion>
Expand All @@ -17,6 +16,7 @@
<SelfContained>True</SelfContained>
<IsPublishable>True</IsPublishable>

<IsTrimmable>False</IsTrimmable>
<IsAotCompatible>False</IsAotCompatible>
</PropertyGroup>

Expand Down
12 changes: 6 additions & 6 deletions src/Runtime/Types/BaseTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using EzrSquared.Runtime.Types.Core.Numerics;
using EzrSquared.Runtime.Types.Core.Text;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection;
Expand All @@ -26,7 +26,7 @@ internal class EzrRuntimeInvalidObject : EzrObject
/// <summary>
/// Creates a new <see cref="EzrRuntimeInvalidObject"/>.
/// </summary>
EzrRuntimeInvalidObject() : base(Context.Empty, Context.Empty, Position.None, Position.None) { }
private EzrRuntimeInvalidObject() : base(Context.Empty, Context.Empty, Position.None, Position.None) { }
}

/// <summary>
Expand All @@ -37,7 +37,7 @@ public abstract class EzrObject : IEzrObject
/// <summary>
/// Cache of reflection data for all <see cref="EzrObject"/> types.
/// </summary>
internal static readonly Lazy<Dictionary<(Type ParentType, string MemberName), MemberInfo>> s_memberMap = new();
internal static readonly ConcurrentDictionary<(Type ParentType, string MemberName), MemberInfo> s_reflectionCache = [];

/// <summary>
/// Gets cached reflection data about a member of <typeparamref name="TParentType"/>.
Expand All @@ -54,14 +54,14 @@ public abstract class EzrObject : IEzrObject
Type parentType = typeof(TParentType);
(Type, string) key = (parentType, name);

if (s_memberMap.Value.TryGetValue(key, out MemberInfo? cachedMemberInfo))
if (s_reflectionCache.TryGetValue(key, out MemberInfo? cachedMemberInfo))
return (TMemberInfo)cachedMemberInfo;

MemberInfo[] members = parentType.GetMember(name);
if (members.Length == 0 || members[0] is not TMemberInfo memberInfo)
return default;
return null;

s_memberMap.Value[key] = memberInfo;
s_reflectionCache[key] = memberInfo;
return memberInfo;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ public static void GetRaw(SharpMethodParameters arguments)
{
RuntimeResult result = arguments.Result;
IEzrObject objectToWrap = arguments.ArgumentReferences["to_wrap"].Object;

IEzrObject wrapped = new EzrSharpCompatibilityObjectInstance(objectToWrap, objectToWrap.GetType(), arguments.ExecutionContext, arguments.StartPosition, arguments.EndPosition);
result.Success(ReferencePool.Get(wrapped, AccessMod.PrivateConstant));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using EzrSquared.Runtime.WrapperAttributes;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
Expand All @@ -16,8 +17,14 @@ namespace EzrSquared.Runtime.Types.CSharpWrappers.CompatWrappers;
/// Parent class for all automatic wrappers which wrap existing C# objects and members so that they can be used in ezr².
/// </summary>
/// <typeparam name="TMemberInfo">The <see cref="MemberInfo"/> type for the C# member being wrapped.</typeparam>

#if NET7_0_OR_GREATER
public abstract partial class EzrSharpCompatibilityWrapper<TMemberInfo> : EzrObject
where TMemberInfo : MemberInfo
#else
public abstract class EzrSharpCompatibilityWrapper<TMemberInfo> : EzrObject
where TMemberInfo : MemberInfo
#endif
{
/// <summary>
/// Reflection info for <see cref="Task.FromResult{TResult}(TResult)"/>/
Expand Down Expand Up @@ -66,8 +73,6 @@ public EzrSharpCompatibilityWrapper(TMemberInfo wrappedMember, object? instance,
Instance = instance;
}


#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
/// <summary>
/// Regex for converting PascalCase/camelCase to snake_case.
/// </summary>
Expand All @@ -77,13 +82,12 @@ public EzrSharpCompatibilityWrapper(TMemberInfo wrappedMember, object? instance,
/// 2. (?&lt;=[a-z0-9])(?=[A-Z]) - Add underscore when transitioning from lowercase or number to uppercase.<br/>
/// 3. (?&lt;=[A-Z])(?=[A-Z][a-z]) - Add underscore between uppercase sequences followed by lowercase (e.g., "TCProtocol").
/// </remarks>
private static readonly Regex s_caseConverterRegex = new(@"(?<!^)(?=[A-Z][a-z])|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex s_caseConverterRegex = GetCaseConverterRegex();

/// <summary>
/// Regex for matching non-alphanumeric + underscore characters.
/// </summary>
private static readonly Regex s_alphaNumericUnderscoreOnlyFilterRegex = new(@"[^a-zA-Z0-9_]", RegexOptions.Compiled | RegexOptions.CultureInvariant);
#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
private static readonly Regex s_alphaNumericUnderscoreOnlyFilterRegex = GetAlphaNumericUnderscoreOnlyFilterRegex();

/// <summary>
/// Converts a string from PascalCase to snake_case.
Expand Down Expand Up @@ -145,6 +149,17 @@ public EzrSharpCompatibilityWrapper(TMemberInfo wrappedMember, object? instance,
result.Failure(new EzrValueOutOfRangeError($"Expected value to be between {long.MinValue} - {long.MaxValue} (inclusive)!", Context, value.StartPosition, value.EndPosition));
break;

case TypeCode.Object when typeof(BigInteger).IsAssignableFrom(targetType):
if (value is EzrInteger or EzrFloat)
{
return value is EzrInteger ezrInteger
? ezrInteger.Value
: new BigInteger(((EzrFloat)value).Value);
}

result.Failure(new EzrUnexpectedTypeError($"Expected integer or float, but got object of type \"{value.TypeName}\"!", Context, value.StartPosition, value.EndPosition));
break;

case TypeCode.UInt16:
double outputUShort = (value as EzrFloat)?.Value ?? ((EzrInteger)value).GetDoubleRepresentation();
if (outputUShort is >= ushort.MinValue and <= ushort.MaxValue)
Expand Down Expand Up @@ -245,7 +260,10 @@ public EzrSharpCompatibilityWrapper(TMemberInfo wrappedMember, object? instance,
return HandleEzrArrayLikeToCSharp(value, targetType, result);

case TypeCode.Object when targetType == typeof(Task):
return Task.CompletedTask;
if (value is EzrSharpCompatibilityObjectInstance taskWrapper && targetType.IsAssignableFrom(taskWrapper.SharpMember))
return (Task)taskWrapper.Instance!;

return s_taskFromResultMethod.MakeGenericMethod(value.GetType()).Invoke(null, [value]);

case TypeCode.Object when typeof(Task).IsAssignableFrom(targetType) && !targetType.IsGenericTypeDefinition:
Type taskTargetType = targetType.GetGenericArguments()[0];
Expand Down Expand Up @@ -362,6 +380,9 @@ protected internal void CSharpToEzrObject(object? value, RuntimeResult result)
case TypeCode.String:
result.Success(NewStringConstant((string)value));
break;
case TypeCode.Object when typeof(BigInteger).IsAssignableFrom(valueType):
result.Success(NewIntegerConstant((BigInteger)value));
break;
case TypeCode.Object when valueType.IsArray && valueType.HasElementType:
HandleCSharpArrayToEzrObject((Array)value, valueType, result);
break;
Expand Down Expand Up @@ -442,4 +463,23 @@ public override int ComputeHashCode(RuntimeResult result)
? HashCode.Combine(HashTag, SharpMember, Instance)
: HashCode.Combine(HashTag, SharpMember);
}


#if NET7_0_OR_GREATER
[GeneratedRegex(@"(?<!^)(?=[A-Z][a-z])|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
private static partial Regex GetCaseConverterRegex();

[GeneratedRegex(@"[^a-zA-Z0-9_]", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
private static partial Regex GetAlphaNumericUnderscoreOnlyFilterRegex();
#else
private static Regex GetCaseConverterRegex()
{
return new Regex(@"(?<!^)(?=[A-Z][a-z])|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", RegexOptions.Compiled | RegexOptions.CultureInvariant);
}

private static Regex GetAlphaNumericUnderscoreOnlyFilterRegex()
{
return new Regex(@"[^a-zA-Z0-9_]", RegexOptions.Compiled | RegexOptions.CultureInvariant);
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,6 @@ protected internal Dictionary<string, IEzrObject> ArgumentsArrayToDictionary(Ref
if (result.ShouldReturn)
return [];

if (primitiveArgument?.GetType() != parameter.ParameterType)
{
result.Failure(new EzrUnexpectedTypeError($"CSharp argument \"{ParameterNames[i]}\" expected value of CSharp type \"{parameter.ParameterType.Name}\", but got object of type \"{argument.TypeName}\"!", Context, argument.StartPosition, argument.EndPosition));
return [];
}

formattedArguments[i] = primitiveArgument;
arguments.Remove(ParameterNames[i]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override void Execute(Reference[] arguments, Interpreter interpreter, Run
result.Failure(new EzrUnexpectedArgumentError($"Only expected 0 (for getting the value) or 1 (for setting the value) argument(s) for CSharp field wrapper \"{SharpMemberName}\"!", Context, StartPosition, EndPosition));
break;

case { Length: 1 } when AutoWrapperAttribute?.IsReadOnly == true:
case { Length: 1 } when AutoWrapperAttribute?.IsReadOnly == true || SharpMember.IsInitOnly:
result.Failure(new EzrIllegalOperationError($"Cannot set value to CSharp field wrapper \"{SharpMemberName}\" as it is read-only!", Context, StartPosition, EndPosition));
break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@ public partial class EzrSharpSourceFunctionWrapper : EzrSharpSourceExecutableWra
/// Creates a new <see cref="EzrSharpSourceFunctionWrapper"/> from a function's <see cref="MethodInfo"/>.
/// </summary>
/// <param name="function">The method to wrap.</param>
/// <param name="instance">The object which contains the method, <see langword="null"/> if static.</param>
/// <param name="parentContext">The context in which this object was created.</param>
/// <param name="startPosition">The starting position of the object.</param>
/// <param name="endPosition">The ending position of the object.</param>
public EzrSharpSourceFunctionWrapper(MethodInfo function, Context parentContext, Position startPosition, Position endPosition) : base(parentContext, startPosition, endPosition)
public EzrSharpSourceFunctionWrapper(MethodInfo function, object? instance, Context parentContext, Position startPosition, Position endPosition) : base(parentContext, startPosition, endPosition)
{
Exception? parameterException = SharpMethodWrapperAttribute.ValidateMethodParameters(function);
if (parameterException is not null)
throw parameterException;

SharpFunction = (EzrSharpSourceWrappableMethod)function.CreateDelegate(typeof(EzrSharpSourceWrappableMethod));
SharpFunction = (EzrSharpSourceWrappableMethod)function.CreateDelegate(typeof(EzrSharpSourceWrappableMethod), instance);
(SharpFunctionName, SharpMethodWrapperAttribute attribute) = GetFunctionInfo(function);

AddParameters(attribute);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public EzrSharpSourceTypeWrapper(
{
SharpType = type;

// Check if type inherits from IEzrObject.
if (!typeof(IEzrObject).IsAssignableFrom(SharpType))
throw new ArgumentException($"A source wrapper cannot be used to wrap \"{type.Name}\", as it does not inherit {nameof(IEzrObject)}!", nameof(type));

// Check if generic or abstract.
if (type.IsAbstract || type.IsGenericTypeDefinition)
throw new ArgumentException($"Cannot wrap generic/abstract C# type \"{type.Name}\"!", nameof(type));
Expand All @@ -82,12 +86,8 @@ public EzrSharpSourceTypeWrapper(
if (typeAttributeException is not null)
throw typeAttributeException;

Exception? parameterException = SharpMethodWrapperAttribute.ValidateMethodParameters(constructor!.Value.Info);
if (parameterException is not null)
throw parameterException;

// Get constructor parameters.
int requiredParameters = constructor.Value.Attribute.RequiredParameters.Length;
int requiredParameters = constructor!.Value.Attribute.RequiredParameters.Length;
Parameters = new (string Name, bool IsRequired)[constructor.Value.Attribute.RequiredParameters.Length + constructor.Value.Attribute.OptionalParameters.Length];

// Required parameters.
Expand Down Expand Up @@ -115,7 +115,7 @@ public EzrSharpSourceTypeWrapper(
// Check if method can be wrapped.
if (method.GetCustomAttribute<SharpMethodWrapperAttribute>(false) is not null)
{
EzrSharpSourceFunctionWrapper sourceMethod = new(method, Context, StartPosition, EndPosition);
EzrSharpSourceFunctionWrapper sourceMethod = new(method, null, Context, StartPosition, EndPosition);
(wrappedMethod, methodName) = (sourceMethod, sourceMethod.SharpFunctionName);
}
else if (method.GetCustomAttribute<SharpAutoWrapperAttribute>() is not null)
Expand Down
2 changes: 1 addition & 1 deletion src/Runtime/Types/Core/RuntimeErrors/EzrAssertionError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public class EzrAssertionError(Context context, Position startPosition, Position
/// Wrapper constructor for creating the error object.
/// </summary>
/// <param name="arguments">The constructor arguments.</param>
[SharpMethodWrapper()]
[SharpMethodWrapper]
public EzrAssertionError(SharpMethodParameters arguments) : this(arguments.ExecutionContext, arguments.StartPosition, arguments.EndPosition) { }
}
3 changes: 2 additions & 1 deletion src/Runtime/WrapperAttributes/SharpTypeWrapperAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class SharpTypeWrapperAttribute(string name) : Attribute
SharpMethodWrapperAttribute? attribute = constructorInfo.GetCustomAttribute<SharpMethodWrapperAttribute>(false);
if (constructor is not null && attribute is not null)
return new AmbiguousMatchException($"Found multiple constructors for type \"{typeInfo.Name}\" with attribute {nameof(SharpMethodWrapperAttribute)}");
else if (attribute is not null)

if (attribute is not null)
{
constructor = (constructorInfo, attribute);
if (SharpMethodWrapperAttribute.ValidateMethodParameters(constructorInfo) is Exception exception)
Expand Down
9 changes: 4 additions & 5 deletions src/Shell/Shell.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<Platforms>AnyCPU</Platforms>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>13</LangVersion>
Expand All @@ -15,6 +14,10 @@

<SelfContained>True</SelfContained>
<IsPublishable>True</IsPublishable>
<PublishAot>False</PublishAot>

<PublishTrimmed>True</PublishTrimmed>
<TrimMode>Partial</TrimMode>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -37,10 +40,6 @@
<RepositoryUrl>https://github.com/uralstech/ezrSquared</RepositoryUrl>
</PropertyGroup>

<PropertyGroup Condition=" '$(Platform)' == 'x64' Or '$(Platform)' == 'arm64' ">
<PublishAot>True</PublishAot>
</PropertyGroup>

<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
<Reference Include="ezrSquared-lib">
<HintPath>..\..\Binaries\ezrSquared\Release\net9.0\ezrSquared-lib.dll</HintPath>
Expand Down
34 changes: 26 additions & 8 deletions src/Syntax/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ private void ReverseTo(int index)
/// <returns>Any <see cref="EzrSyntaxError"/> that occurred in the lexing; <see langword="null"/> if none occurred.</returns>
public EzrSyntaxError? Tokenize(out List<Token> tokens)
{
EzrSyntaxError? error;
tokens = [];
while (!_reachedEnd)
{
Expand All @@ -109,7 +108,7 @@ private void ReverseTo(int index)
case '"':
case '`':
case '\'':
tokens.Add(CompileStringLike(out error));
tokens.Add(CompileStringLike(out EzrSyntaxError? error));
if (error is not null)
return error;
break;
Expand Down Expand Up @@ -238,9 +237,13 @@ private void ReverseTo(int index)
tokens.Add(new Token(TokenType.Tilde, TokenTypeGroup.Symbol, string.Empty, _position.Copy()));
Advance();
break;
case '#':
case '_':
case char current when char.IsLetter(current):
tokens.Add(CompileIdentifier());
tokens.Add(CompileIdentifier(out error));
if (error is not null)
return error;

break;
case char current when char.IsDigit(current):
tokens.Add(CompileNumber());
Expand Down Expand Up @@ -583,21 +586,36 @@ private Token CompileColon()
/// <summary>
/// Creates <see cref="TokenType.Identifier"/>, keyword type (<see cref="TokenType.KeywordItem"/>, <see cref="TokenType.KeywordFunction"/>, etc) and qeyword type (<see cref="TokenType.QeywordC"/>, <see cref="TokenType.QeywordFd"/>, etc) <see cref="Token"/> objects.
/// </summary>
/// <param name="error">Any <see cref="EzrSyntaxError"/> that occurred in the process; <see langword="null"/> if none occurred.</param>
/// <returns>The created <see cref="Token"/>.</returns>
private Token CompileIdentifier()
private Token CompileIdentifier(out EzrSyntaxError? error)
{
Position startPosition = _position.Copy();
StringBuilder idValue = new();
error = null;

bool isEscapedIdentifier = _currentChar == '#';
if (isEscapedIdentifier)
{
Advance();
if (!char.IsLetterOrDigit(_currentChar) && _currentChar != '_')
{
error = new EzrSyntaxError(EzrSyntaxError.UnexpectedCharacter, "The hash symbol should only be used before identifiers to escape keyword detection.", startPosition, _position);
return Token.Empty;
}
}

StringBuilder idValue = new();
while (!_reachedEnd && (char.IsLetterOrDigit(_currentChar) || _currentChar == '_'))
{
idValue.Append(_currentChar);
Advance();
}

string original = idValue.ToString();
return original.ToLower() switch
{
return isEscapedIdentifier
? new Token(TokenType.Identifier, TokenTypeGroup.Special, original, startPosition, _position.Copy())
: original.ToLower() switch
{
"private" => new Token(TokenType.KeywordPrivate, TokenTypeGroup.Keyword, string.Empty, startPosition, _position.Copy()),
"constant" => new Token(TokenType.KeywordConstant, TokenTypeGroup.Keyword, string.Empty, startPosition, _position.Copy()),
"readonly" => new Token(TokenType.KeywordReadonly, TokenTypeGroup.Keyword, string.Empty, startPosition, _position.Copy()),
Expand Down Expand Up @@ -658,6 +676,6 @@ private Token CompileIdentifier()
"g" => new Token(TokenType.QeywordG, TokenTypeGroup.Qeyword, original, startPosition, _position.Copy()),
"v" => new Token(TokenType.QeywordV, TokenTypeGroup.Qeyword, original, startPosition, _position.Copy()),
_ => new Token(TokenType.Identifier, TokenTypeGroup.Special, original, startPosition, _position.Copy()),
};
};
}
}

0 comments on commit efc6e59

Please sign in to comment.