diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c134ace..93f4734f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,7 +110,7 @@ jobs: publish-nuget: name: Publish as dotnet tool to NuGet - runs-on: ubuntu-latest + runs-on: windows-latest needs: publish-release steps: - name: Checkout @@ -120,10 +120,10 @@ jobs: with: dotnet-version: 10.0.x - name: Build - run: dotnet build ./src/HydraScript/HydraScript.csproj -c Release -v n \ - /p:Version=${{ needs.publish-release.outputs.determined_version }} \ + run: dotnet build ./src/HydraScript/HydraScript.csproj -c Release -v n ` + /p:Version=${{ needs.publish-release.outputs.determined_version }} ` /p:PublishAot=false /p:PublishSingleFile=false - name: Publish - run: dotnet nuget push ./src/HydraScript/bin/Release/*.nupkg \ - --api-key ${{ secrets.NUGET_API_KEY }} \ + run: dotnet nuget push ./src/HydraScript/bin/Release/*.nupkg ` + --api-key ${{ secrets.NUGET_API_KEY }} ` --source https://api.nuget.org/v3/index.json \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 84754d44..0e3eb7ec 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,18 +1,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + diff --git a/Readme.md b/Readme.md index 976b7d86..14b84f5a 100644 --- a/Readme.md +++ b/Readme.md @@ -84,6 +84,20 @@ type composite = { } ``` +#### Strings + +Strings support following operations: +- index access, returns `string`: +``` +let str = "str" +>>> str[1] // t +``` +- length getter: +``` +let str = "str" +>>> ~str // 3 +``` + ### Variables For declaring mutable variables use `let`: @@ -165,8 +179,8 @@ array = array ++ [5, 7] // concatenation | ! | unary | boolean | boolean | | - | unary | number | number | | ++ | binary | [] | [] | -| :: | binary | [] и number | void | -| ~ | unary | [] | number | +| :: | binary | [] and number | void | +| ~ | unary | [] or string | number | ### Conditionals diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs deleted file mode 100644 index ba34c898..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis; - -public interface IDefaultValueForTypeCalculator -{ - public object? GetDefaultValueForType(Type type); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IExplicitCastValidator.cs b/src/Application/HydraScript.Application.StaticAnalysis/IExplicitCastValidator.cs deleted file mode 100644 index 97efe0ff..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/IExplicitCastValidator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis; - -public interface IExplicitCastValidator -{ - bool IsAllowed(Type from, Type to); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs new file mode 100644 index 00000000..cabbd2d3 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs @@ -0,0 +1,22 @@ +namespace HydraScript.Application.StaticAnalysis; + +public interface IHydraScriptTypesService +{ + public Type Number { get; } + + public Type Boolean { get; } + + public Type String { get; } + + public Type Undefined { get; } + + public Type Void { get; } + + public IEnumerable GetDefaultTypes(); + + public bool Contains(Type type); + + public object? GetDefaultValueForType(Type type); + + public bool IsExplicitCastAllowed(Type from, Type to); +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs deleted file mode 100644 index 42e79dac..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis; - -public interface IJavaScriptTypesProvider -{ - public IEnumerable GetDefaultTypes(); - - public bool Contains(Type type); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs b/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs index d7ac8ff2..99553c85 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/ITypeDeclarationsResolver.cs @@ -7,4 +7,6 @@ public interface ITypeDeclarationsResolver public void Store(TypeDeclaration declaration); public void Resolve(); + + IHydraScriptTypesService TypesService { get; } } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs deleted file mode 100644 index 3812ba6d..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs +++ /dev/null @@ -1,26 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal class DefaultValueForTypeCalculator : IDefaultValueForTypeCalculator -{ - public object? GetDefaultValueForType(Type type) - { - if (type is NullableType) - return null; - if (type.Equals("boolean")) - return false; - if (type.Equals("number")) - return 0; - if (type.Equals("string")) - return string.Empty; - if (type.Equals("void")) - return new object(); - if (type.Equals(new NullType())) - return null; - if (type is ArrayType) - return new List(); - - return new object(); - } -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs deleted file mode 100644 index 8e581ddc..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal sealed class ExplicitCastValidator : IExplicitCastValidator -{ - private static readonly Type Boolean = "boolean"; - private static readonly Type Number = "number"; - private static readonly Type String = "string"; - private static readonly Any Any = new(); - - private readonly Dictionary> _allowedConversions = new() - { - { String, [Any] }, - { Number, [String, Boolean] }, - { Boolean, [String, Number] }, - }; - - public bool IsAllowed(Type from, Type to) - { - var typeEqualityComparer = default(CommutativeTypeEqualityComparer); - - if (typeEqualityComparer.Equals(from, to)) - return true; - - if (!_allowedConversions.TryGetValue(to, out var allowedFrom)) - return false; - - for (var i = 0; i < allowedFrom.Count; i++) - { - if (typeEqualityComparer.Equals(allowedFrom[i], from)) - return true; - } - - return false; - } -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs new file mode 100644 index 00000000..013ace91 --- /dev/null +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs @@ -0,0 +1,83 @@ +using HydraScript.Domain.IR.Types; + +namespace HydraScript.Application.StaticAnalysis.Impl; + +internal class HydraScriptTypesService : IHydraScriptTypesService +{ + private readonly HashSet _types; + private readonly Dictionary> _allowedConversions; + + public HydraScriptTypesService() + { + _types = + [ + Number, + Boolean, + String, + Null, + Undefined, + Void + ]; + _allowedConversions = new() + { + [String] = [new Any()], + [Number] = [String, Boolean], + [Boolean] = [String, Number], + }; + } + + public Type Number => NumberType.Instance; + + public Type Boolean => BooleanType.Instance; + + public Type String => StringType.Instance; + + private Type Null => NullType.Instance; + + public Type Undefined => "undefined"; + + public Type Void => "void"; + + public IEnumerable GetDefaultTypes() => _types; + + public bool Contains(Type type) => _types.Contains(type); + + public object? GetDefaultValueForType(Type type) + { + if (type is NullableType) + return null; + if (type.Equals(Boolean)) + return false; + if (type.Equals(Number)) + return 0; + if (type.Equals(String)) + return string.Empty; + if (type.Equals(Void)) + return new object(); + if (type.Equals(Null)) + return null; + if (type is ArrayType) + return new List(); + + return new object(); + } + + public bool IsExplicitCastAllowed(Type from, Type to) + { + var typeEqualityComparer = default(CommutativeTypeEqualityComparer); + + if (typeEqualityComparer.Equals(from, to)) + return true; + + if (!_allowedConversions.TryGetValue(to, out var allowedFrom)) + return false; + + for (var i = 0; i < allowedFrom.Count; i++) + { + if (typeEqualityComparer.Equals(allowedFrom[i], from)) + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs deleted file mode 100644 index 425a08a5..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal class JavaScriptTypesProvider : IJavaScriptTypesProvider -{ - private readonly HashSet _types = - [ - "number", - "boolean", - "string", - new NullType(), - "undefined", - "void" - ]; - - public IEnumerable GetDefaultTypes() => _types; - - public bool Contains(Type type) => _types.Contains(type); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs index 19506663..20f174fd 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/StandardLibraryProvider.cs @@ -4,18 +4,13 @@ namespace HydraScript.Application.StaticAnalysis.Impl; -internal class StandardLibraryProvider : IStandardLibraryProvider +internal class StandardLibraryProvider(IHydraScriptTypesService typesService) : IStandardLibraryProvider { - private readonly IJavaScriptTypesProvider _provider; - - public StandardLibraryProvider(IJavaScriptTypesProvider provider) => - _provider = provider; - public ISymbolTable GetStandardLibrary() { var library = new SymbolTable(); - foreach (var type in _provider.GetDefaultTypes()) + foreach (var type in typesService.GetDefaultTypes()) library.AddSymbol(new TypeSymbol(type)); var symbolTable = new SymbolTable(); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs index f75c8b42..6c2e1bd0 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/TypeDeclarationsResolver.cs @@ -5,7 +5,7 @@ namespace HydraScript.Application.StaticAnalysis.Impl; internal class TypeDeclarationsResolver( - IJavaScriptTypesProvider provider, + IHydraScriptTypesService typesService, ISymbolTableStorage symbolTables, IVisitor typeBuilder) : ITypeDeclarationsResolver { @@ -16,7 +16,7 @@ public void Store(TypeDeclaration declaration) => public void Resolve() { - var defaults = provider.GetDefaultTypes() + var defaults = TypesService.GetDefaultTypes() .AsValueEnumerable() .Select(x => new TypeSymbol(x)) .ToList(); @@ -43,4 +43,6 @@ public void Resolve() } } } + + public IHydraScriptTypesService TypesService { get; } = typesService; } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs index e2ec4a80..ae8dd7b3 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs @@ -19,9 +19,7 @@ public static IServiceCollection AddStaticAnalysis(this IServiceCollection servi services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs index 37308f5d..7bb0f789 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs @@ -14,6 +14,7 @@ internal class DeclarationVisitor : VisitorNoReturnBase IVisitor, IVisitor { + private readonly IHydraScriptTypesService _typesService; private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; private readonly ISymbolTableStorage _symbolTables; @@ -22,6 +23,7 @@ internal class DeclarationVisitor : VisitorNoReturnBase private readonly IVisitor _returnAnalyzer; public DeclarationVisitor( + IHydraScriptTypesService typesService, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, @@ -29,6 +31,7 @@ public DeclarationVisitor( IVisitor typeBuilder, IVisitor returnAnalyzer) { + _typesService = typesService; _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; @@ -54,9 +57,9 @@ public VisitUnit Visit(LexicalDeclaration visitable) throw new DeclarationAlreadyExists(assignment.Destination.Id); var destinationType = assignment.DestinationType?.Accept( - _typeBuilder) ?? "undefined"; + _typeBuilder) ?? _typesService.Undefined; - if (destinationType == "undefined" && + if (destinationType == _typesService.Undefined && assignment.Source is ImplicitLiteral or ArrayLiteral { Expressions.Count: 0 }) throw visitable.ReadOnly ? new ConstWithoutInitializer(assignment.Destination.Id) @@ -101,12 +104,12 @@ public VisitUnit Visit(FunctionDeclaration visitable) if (parameters is [ObjectType methodOwner, ..] && visitable.Arguments is [{ TypeValue: TypeIdentValue }, ..]) _methodStorage.BindMethod(methodOwner, functionSymbol, functionSymbolId); - if (functionSymbol.Type.Equals("undefined")) + if (functionSymbol.Type.Equals(_typesService.Undefined)) { if (visitable.HasReturnStatement) _functionStorage.Save(functionSymbol, visitable); else - functionSymbol.DefineReturnType("void"); + functionSymbol.DefineReturnType(_typesService.Void); } parentTable.AddSymbol(functionSymbol); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs index b4789433..fa217df2 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -45,36 +45,33 @@ internal class SemanticChecker : VisitorBase, IVisitor, IVisitor { - private readonly IDefaultValueForTypeCalculator _calculator; + private readonly IHydraScriptTypesService _typesService; private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; private readonly ISymbolTableStorage _symbolTables; private readonly IComputedTypesStorage _computedTypes; private readonly IAmbiguousInvocationStorage _ambiguousInvocations; - private readonly IExplicitCastValidator _explicitCastValidator; private readonly IVisitor _typeBuilder; public SemanticChecker( - IDefaultValueForTypeCalculator calculator, + IHydraScriptTypesService typesService, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, IComputedTypesStorage computedTypes, IAmbiguousInvocationStorage ambiguousInvocations, - IExplicitCastValidator explicitCastValidator, IVisitor typeBuilder) { - _calculator = calculator; + _typesService = typesService; _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; _computedTypes = computedTypes; _ambiguousInvocations = ambiguousInvocations; - _explicitCastValidator = explicitCastValidator; _typeBuilder = typeBuilder; } - public override Type Visit(IAbstractSyntaxTreeNode visitable) => "undefined"; + public override Type Visit(IAbstractSyntaxTreeNode visitable) => _typesService.Undefined; public Type Visit(ScriptBody visitable) { @@ -89,30 +86,30 @@ public Type Visit(ScriptBody visitable) _computedTypes.Clear(); _ambiguousInvocations.Clear(); - return "undefined"; + return _typesService.Undefined; } public Type Visit(WhileStatement visitable) { var condType = visitable.Condition.Accept(This); - if (!condType.Equals("boolean")) + if (!condType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, condType); visitable.Statement.Accept(This); - return "undefined"; + return _typesService.Undefined; } public Type Visit(IfStatement visitable) { var testType = visitable.Test.Accept(This); - if (!testType.Equals("boolean")) + if (!testType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, testType); visitable.Then.Accept(This); visitable.Else?.Accept(This); - return "undefined"; + return _typesService.Undefined; } public Type Visit(InsideStatementJump visitable) @@ -135,7 +132,7 @@ public Type Visit(InsideStatementJump visitable) break; } - return "undefined"; + return _typesService.Undefined; } public Type Visit(ReturnStatement visitable) @@ -143,7 +140,7 @@ public Type Visit(ReturnStatement visitable) if (!visitable.ChildOf()) throw new ReturnOutsideFunction(visitable.Segment); - return visitable.Expression?.Accept(This) ?? "void"; + return visitable.Expression?.Accept(This) ?? _typesService.Void; } public Type Visit(ExpressionStatement visitable) => @@ -157,7 +154,7 @@ public Type Visit(IdentifierReference visitable) return symbol?.Type ?? throw new UnknownIdentifierReference(visitable); } - public Type Visit(EnvVarReference visitable) => "string"; + public Type Visit(EnvVarReference visitable) => _typesService.String; public Type Visit(Literal visitable) => visitable.Type.Accept(_typeBuilder); @@ -167,7 +164,7 @@ public Type Visit(ImplicitLiteral visitable) var type = visitable.Type.Accept(_typeBuilder); if (!visitable.IsDefined) { - var definedValue = _calculator.GetDefaultValueForType(type); + var definedValue = _typesService.GetDefaultValueForType(type); visitable.SetValue(definedValue); } @@ -217,7 +214,7 @@ public ObjectType Visit(ObjectLiteral visitable) public Type Visit(ConditionalExpression visitable) { var tType = visitable.Test.Accept(This); - if (!tType.Equals("boolean")) + if (!tType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Test.Segment, tType); var cType = visitable.Consequent.Accept(This); @@ -238,82 +235,61 @@ public Type Visit(BinaryExpression visitable) var lType = visitable.Left.Accept(This); var rType = visitable.Right.Accept(This); - if (visitable.Operator != "::" && !lType.Equals(rType)) + if (!lType.TryGetOperator(visitable.Operator, out var @operator)) + throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator); + + var operation = new OperationDescriptor(visitable.Operator, OperandTypes: [lType, rType]); + if (!@operator.TryGetResultType(operation, out var resultType)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: lType, right: rType); - Type number = "number"; - Type @string = "string"; - Type boolean = "boolean"; + if (resultType.Equals(_typesService.Undefined)) + throw new CannotDefineType(visitable.Segment); - return visitable.Operator switch - { - "+" when lType.Equals(number) => number, - "+" when lType.Equals(@string) => @string, - "+" => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "-" or "*" or "/" or "%" => lType.Equals(number) - ? number - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "||" or "&&" => lType.Equals(boolean) - ? boolean - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "==" or "!=" => boolean, - ">" or ">=" or "<" or "<=" => lType.Equals(number) - ? boolean - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "++" when lType is ArrayType { Type: Any } && rType is ArrayType { Type: Any } => - throw new CannotDefineType(visitable.Segment), - "++" => lType is ArrayType lArrType && rType is ArrayType rArrType - ? lArrType.Type is not Any ? lArrType : rArrType.Type is not Any ? rArrType : throw new CannotDefineType(visitable.Segment) - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" when lType is not ArrayType => - throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" => rType.Equals(number) ? "void" : throw new ArrayAccessException(visitable.Segment, rType), - _ => "undefined" - }; + return resultType; } public Type Visit(UnaryExpression visitable) { var eType = visitable.Expression.Accept(This); - Type number = "number"; - Type boolean = "boolean"; + if (!eType.TryGetOperator(visitable.Operator, out var @operator)) + throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator); - return visitable.Operator switch - { - "-" when eType.Equals(number) => number, - "!" when eType.Equals(boolean) => boolean, - "~" when eType is ArrayType => number, - _ => throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator) - }; + var operation = new OperationDescriptor(visitable.Operator, OperandTypes: [eType]); + if (!@operator.TryGetResultType(operation, out var resultType)) + throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator); + + if (resultType.Equals(_typesService.Undefined)) + throw new CannotDefineType(visitable.Segment); + + return resultType; } public Type Visit(LexicalDeclaration visitable) { - Type undefined = "undefined"; - for (var i = 0; i < visitable.Assignments.Count; i++) { var assignment = visitable.Assignments[i]; var registeredSymbol = _symbolTables[visitable.Scope].FindSymbol(new VariableSymbolId(assignment.Destination.Id))!; var sourceType = assignment.Source.Accept(This); - if (sourceType.Equals(undefined)) + if (sourceType.Equals(_typesService.Undefined)) throw new CannotDefineType(assignment.Source.Segment); - if (sourceType.Equals("void")) + if (sourceType.Equals(_typesService.Void)) throw new CannotAssignVoid(assignment.Source.Segment); - if (!registeredSymbol.Type.Equals(undefined) && !registeredSymbol.Type.Equals(sourceType)) + if (!registeredSymbol.Type.Equals(_typesService.Undefined) && + !default(CommutativeTypeEqualityComparer).Equals(registeredSymbol.Type, sourceType)) throw new IncompatibleTypesOfOperands( assignment.Segment, left: registeredSymbol.Type, right: sourceType); - if (sourceType is NullType && registeredSymbol.Type.Equals(undefined)) + if (sourceType is NullType && registeredSymbol.Type.Equals(_typesService.Undefined)) throw new CannotAssignNullWhenUndefined(assignment.Segment); - var actualType = registeredSymbol.Type.Equals(undefined) + var actualType = registeredSymbol.Type.Equals(_typesService.Undefined) ? sourceType : registeredSymbol.Type; var actualSymbol = actualType switch @@ -325,11 +301,13 @@ public Type Visit(LexicalDeclaration visitable) _symbolTables[visitable.Scope].AddSymbol(actualSymbol); } - return undefined; + return _typesService.Undefined; } public Type Visit(AssignmentExpression visitable) { + var typeComparer = default(CommutativeTypeEqualityComparer); + if (visitable.Destination is CallExpression) throw new WrongAssignmentTarget(visitable.Destination); @@ -337,7 +315,7 @@ public Type Visit(AssignmentExpression visitable) if (!visitable.Destination.Empty()) { var destinationType = visitable.Destination.Accept(This); - if (!destinationType.Equals(sourceType)) + if (!typeComparer.Equals(destinationType, sourceType)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: destinationType, @@ -349,12 +327,12 @@ public Type Visit(AssignmentExpression visitable) var symbol = visitable.Destination.Id.ToValueDto().Type is ValueDtoType.Name ? _symbolTables[visitable.Scope].FindSymbol(new VariableSymbolId(visitable.Destination.Id)) ?? throw new UnknownIdentifierReference(visitable.Destination.Id) - : new VariableSymbol(visitable.Destination.Id, "string"); + : new VariableSymbol(visitable.Destination.Id, _typesService.String); if (symbol.ReadOnly) throw new AssignmentToConst(visitable.Destination.Id); - if (!sourceType.Equals(symbol.Type)) + if (!typeComparer.Equals(sourceType, symbol.Type)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: symbol.Type, @@ -368,7 +346,7 @@ public Type Visit(MemberExpression visitable) IAbstractSyntaxTreeNode id = visitable.Id; var idType = id.Accept(This); visitable.ComputedIdTypeGuid = _computedTypes.Save(idType); - return visitable.Empty() ? idType : visitable.AccessChain?.Accept(This) ?? "undefined"; + return visitable.Empty() ? idType : visitable.AccessChain?.Accept(This) ?? _typesService.Undefined; } public Type Visit(IndexAccess visitable) @@ -378,16 +356,16 @@ public Type Visit(IndexAccess visitable) ?? (visitable.Parent as MemberExpression)!.ComputedIdTypeGuid; var prevType = _computedTypes.Get(prevTypeGuid); - if (prevType is not ArrayType arrayType) + if (!prevType.TryGetOperator("[]", out var indexOperator)) throw new NonAccessibleType(prevType); var indexType = visitable.Index.Accept(This); - if (!indexType.Equals("number")) + var indexAccessDescriptor = new OperationDescriptor("[]", [prevType, indexType]); + if (!indexOperator.TryGetResultType(indexAccessDescriptor, out var elemType)) throw new ArrayAccessException(visitable.Segment, indexType); - var elemType = arrayType.Type; visitable.ComputedTypeGuid = _computedTypes.Save(elemType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? "undefined" : elemType; + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : elemType; } public Type Visit(DotAccess visitable) @@ -407,7 +385,7 @@ public Type Visit(DotAccess visitable) ? objectType : throw new ObjectAccessException(visitable.Segment, objectType, visitable.Property); visitable.ComputedTypeGuid = _computedTypes.Save(fieldType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? "undefined" : fieldType; + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : fieldType; } public ObjectType Visit(WithExpression visitable) @@ -435,19 +413,19 @@ public Type Visit(CastAsExpression visitable) { var from = visitable.Expression.Accept(This); - if (from.Equals("undefined")) + if (from.Equals(_typesService.Undefined)) throw new CannotDefineType(visitable.Expression.Segment); var to = visitable.Cast.Accept(_typeBuilder); visitable.ToType = to switch { - _ when to.Equals("string") => CastAsExpression.DestinationType.String, - _ when to.Equals("number") => CastAsExpression.DestinationType.Number, - _ when to.Equals("boolean") => CastAsExpression.DestinationType.Boolean, + _ when to.Equals(_typesService.String) => CastAsExpression.DestinationType.String, + _ when to.Equals(_typesService.Number) => CastAsExpression.DestinationType.Number, + _ when to.Equals(_typesService.Boolean) => CastAsExpression.DestinationType.Boolean, _ => CastAsExpression.DestinationType.Undefined }; - return _explicitCastValidator.IsAllowed(from, to) + return _typesService.IsExplicitCastAllowed(from, to) ? to : throw new ExplicitCastNotSupported(visitable, from, to); } @@ -490,14 +468,13 @@ public Type Visit(CallExpression visitable) throw new WrongTypeOfArgument(expr.Segment, expectedType, actualType); }); - if (functionSymbol.Type.Equals("undefined")) + if (functionSymbol.Type.Equals(_typesService.Undefined)) { var declaration = _functionStorage.Get(functionSymbol); functionReturnType = declaration.Accept(This); } - Type @void = "void"; - if (!functionReturnType.Equals(@void)) + if (!functionReturnType.Equals(_typesService.Void)) visitable.HasReturnValue = true; return functionReturnType; } @@ -509,26 +486,24 @@ public Type Visit(FunctionDeclaration visitable) _functionStorage.RemoveIfPresent(symbol); visitable.Statements.Accept(This); - Type undefined = "undefined"; HashSet returnTypes = []; for (var i = 0; i < visitable.ReturnStatements.Count; i++) { var returnStatementType = visitable.ReturnStatements[i].Accept(This); returnTypes.Add(returnStatementType); - if (returnTypes.Count > 1 && symbol.Type.Equals(undefined)) + if (returnTypes.Count > 1 && symbol.Type.Equals(_typesService.Undefined)) throw new CannotDefineType(visitable.Segment); - if (!symbol.Type.Equals(undefined) && !symbol.Type.Equals(returnStatementType)) + if (!symbol.Type.Equals(_typesService.Undefined) && !symbol.Type.Equals(returnStatementType)) throw new WrongReturnType( visitable.ReturnStatements[i].Segment, expected: symbol.Type, actual: returnStatementType); } - if (symbol.Type.Equals(undefined)) + if (symbol.Type.Equals(_typesService.Undefined)) symbol.DefineReturnType(returnTypes.Single()); - Type @void = "void"; - if (!symbol.Type.Equals(@void) && !visitable.AllCodePathsEndedWithReturn) + if (!symbol.Type.Equals(_typesService.Void) && !visitable.AllCodePathsEndedWithReturn) throw new FunctionWithoutReturnStatement(visitable.Segment); if (symbol.Type is NullType) @@ -541,21 +516,21 @@ public Type Visit(BlockStatement visitable) { for (var i = 0; i < visitable.Count; i++) visitable[i].Accept(This); - return "undefined"; + return _typesService.Undefined; } public Type Visit(OutputStatement visitable) { visitable.Expression.Accept(This); - return "undefined"; + return _typesService.Undefined; } public Type Visit(InputStatement visitable) { IAbstractSyntaxTreeNode id = visitable.Destination; var idType = id.Accept(This); - if (!idType.Equals("string")) + if (!idType.Equals(_typesService.String)) throw new UnsupportedOperation(visitable.Segment, idType, "<<<"); - return "undefined"; + return _typesService.Undefined; } } \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs index 7f1e10ce..b6c77fe9 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/TypeSystemLoader.cs @@ -12,16 +12,13 @@ internal class TypeSystemLoader : VisitorNoReturnBase, IVisitor { private readonly ITypeDeclarationsResolver _resolver; - private readonly IJavaScriptTypesProvider _provider; private readonly ISymbolTableStorage _symbolTables; public TypeSystemLoader( ITypeDeclarationsResolver resolver, - IJavaScriptTypesProvider provider, ISymbolTableStorage symbolTables) { _resolver = resolver; - _provider = provider; _symbolTables = symbolTables; } @@ -45,7 +42,7 @@ public VisitUnit Visit(TypeDeclaration visitable) { var symbolTable = _symbolTables[visitable.Scope]; if (symbolTable.ContainsSymbol(new TypeSymbolId(visitable.TypeId)) || - _provider.Contains(visitable.TypeId.Name)) + _resolver.TypesService.Contains(visitable.TypeId.Name)) throw new DeclarationAlreadyExists(visitable.TypeId); symbolTable.AddSymbol( diff --git a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs index 317d1314..b85c3ef0 100644 --- a/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs +++ b/src/Domain/HydraScript.Domain.BackEnd/Impl/Instructions/WithAssignment/Simple.cs @@ -73,7 +73,8 @@ protected virtual void Assign() { "-" => -Convert.ToDouble(value), "!" => !Convert.ToBoolean(value), - "~" => ((List)value!).Count, + "~" when value is List list => list.Count, + "~" when value is string @string => @string.Length, "" => value, _ => throw new NotSupportedException($"_operator {_operator} is not supported") }; @@ -98,9 +99,13 @@ protected virtual void Assign() ">=" => Convert.ToDouble(lValue) >= Convert.ToDouble(rValue), "<" => Convert.ToDouble(lValue) < Convert.ToDouble(rValue), "<=" => Convert.ToDouble(lValue) <= Convert.ToDouble(rValue), - "." => ((Dictionary)lValue!)[rValue!.ToString()!], - "[]" => ((List)lValue!)[Convert.ToInt32(rValue)], - "++" => ((List)lValue!).Concat((List)rValue!).ToList(), + "." when lValue is Dictionary @object => @object[rValue!.ToString()!], + "[]" when lValue is List list => list[Convert.ToInt32(rValue)], + "[]" when lValue is string @string => @string.Substring(Convert.ToInt32(rValue), 1), + "++" when lValue is List lList && rValue is List rList => + lList.Concat(rList).ToList(), + "++" when lValue is string lString && rValue is string rString => + ZString.Concat(lString, rString), _ => throw new NotSupportedException($"_operator {_operator} is not supported") }; } diff --git a/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj b/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj index d795af01..535b86ea 100644 --- a/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj +++ b/src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj @@ -1,5 +1,6 @@  + diff --git a/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs b/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs index c80b6ff3..88b265fc 100644 --- a/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs +++ b/src/Domain/HydraScript.Domain.IR/Types/ArrayType.cs @@ -1,6 +1,16 @@ +using HydraScript.Domain.IR.Types.Operators; + namespace HydraScript.Domain.IR.Types; -public class ArrayType(Type type) : Type($"{type}[]") +public class ArrayType(Type type) : + Type( + $"{type}[]", + [ + default(ArrayConcatOperator), + default(LengthOperator), + default(IndexOperator), + default(RemoveFromArrayOperator) + ]) { public Type Type { get; private set; } = type; diff --git a/src/Domain/HydraScript.Domain.IR/Types/BooleanType.cs b/src/Domain/HydraScript.Domain.IR/Types/BooleanType.cs new file mode 100644 index 00000000..0d6fac52 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/BooleanType.cs @@ -0,0 +1,8 @@ +using HydraScript.Domain.IR.Types.Operators; + +namespace HydraScript.Domain.IR.Types; + +public sealed class BooleanType() : Type("boolean", [default(ConditionalOperator)]) +{ + public static readonly BooleanType Instance = new(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/IOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/IOperator.cs new file mode 100644 index 00000000..806a1488 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/IOperator.cs @@ -0,0 +1,12 @@ +namespace HydraScript.Domain.IR.Types; + +public interface IOperator +{ + public IReadOnlyList Values { get; } + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType); +} + +public record struct OperationDescriptor( + string Operator, + IReadOnlyList OperandTypes); \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/NullType.cs b/src/Domain/HydraScript.Domain.IR/Types/NullType.cs index d027894d..c00a18ad 100644 --- a/src/Domain/HydraScript.Domain.IR/Types/NullType.cs +++ b/src/Domain/HydraScript.Domain.IR/Types/NullType.cs @@ -1,7 +1,9 @@ namespace HydraScript.Domain.IR.Types; -public class NullType() : Type("null") +public sealed class NullType() : Type("null") { + public static readonly NullType Instance = new(); + public override bool Equals(Type? obj) => obj is ObjectType or NullableType or NullType or Any; diff --git a/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs b/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs index bb32b231..f98912c5 100644 --- a/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs +++ b/src/Domain/HydraScript.Domain.IR/Types/NullableType.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace HydraScript.Domain.IR.Types; public class NullableType(Type type) : Type($"{type}?") @@ -15,6 +17,9 @@ public override void ResolveReference( Type.ResolveReference(reference, refId, visited); } + public override bool TryGetOperator(string value, [MaybeNullWhen(false)] out IOperator @operator) => + Type.TryGetOperator(value, out @operator); + public override bool Equals(Type? obj) { if (obj is NullableType that) diff --git a/src/Domain/HydraScript.Domain.IR/Types/NumberType.cs b/src/Domain/HydraScript.Domain.IR/Types/NumberType.cs new file mode 100644 index 00000000..702d5f56 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/NumberType.cs @@ -0,0 +1,14 @@ +using HydraScript.Domain.IR.Types.Operators; + +namespace HydraScript.Domain.IR.Types; + +public sealed class NumberType() : + Type( + "number", + [ + default(ArithmeticOperator), + default(ComparisonOperator) + ]) +{ + public static readonly NumberType Instance = new(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs b/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs index 120001f7..6f77a582 100644 --- a/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs +++ b/src/Domain/HydraScript.Domain.IR/Types/ObjectType.cs @@ -1,9 +1,10 @@ using Cysharp.Text; using HydraScript.Domain.IR.Impl.Symbols.Ids; +using ZLinq; namespace HydraScript.Domain.IR.Types; -public class ObjectType : Type +public sealed class ObjectType : Type { private readonly Dictionary _properties; private readonly List _methods = []; @@ -12,7 +13,7 @@ public class ObjectType : Type public string LastAccessedMethodName { get; private set; } = string.Empty; - public ObjectType(Dictionary properties) + public ObjectType(Dictionary properties) : base(string.Empty) { _properties = properties; _hasher = new ObjectTypeHasher(this); @@ -92,33 +93,21 @@ public override string ToString() return result; } - private class ObjectTypeHasher + private sealed class ObjectTypeHasher(ObjectType reference) { - private readonly ObjectType _reference; - - public ObjectTypeHasher(ObjectType reference) => - _reference = reference; - public int HashObjectType(ObjectType objectType) => - objectType._properties.Keys.Select( + objectType._properties.Keys.AsValueEnumerable().Select( key => HashCode.Combine( key, - objectType[key]!.Equals(_reference) + objectType[key]!.Equals(reference) ? "@this".GetHashCode() : HashCode.Combine(key, objectType[key]!.GetType()))) .Aggregate(36, HashCode.Combine); } - private class ObjectTypePrinter + private sealed class ObjectTypePrinter(ObjectType reference) { - private readonly ObjectType _reference; - private readonly ISet _visited; - - public ObjectTypePrinter(ObjectType reference) - { - _reference = reference; - _visited = new HashSet(); - } + private readonly HashSet _visited = []; public void Clear() => _visited.Clear(); @@ -134,7 +123,7 @@ public string PrintObjectType(ObjectType objectType) { if (_visited.Contains(objectType)) return string.Empty; - if (!objectType.Equals(_reference)) + if (!objectType.Equals(reference)) _visited.Add(objectType); using var zsb = ZString.CreateStringBuilder(); @@ -144,7 +133,7 @@ public string PrintObjectType(ObjectType objectType) var type = objectType[key]; var prop = $"{key}: "; - if (type!.Equals(_reference)) + if (type!.Equals(reference)) prop += "@this"; else { @@ -164,7 +153,7 @@ public string PrintObjectType(ObjectType objectType) private string PrintArrayType(ArrayType arrayType) { using var zsb = ZString.CreateStringBuilder(); - zsb.Append(arrayType.Type.Equals(_reference) + zsb.Append(arrayType.Type.Equals(reference) ? "@this" : Print(arrayType.Type)); zsb.Append("[]"); @@ -175,7 +164,7 @@ private string PrintArrayType(ArrayType arrayType) private string PrintNullableType(NullableType nullableType) { using var zsb = ZString.CreateStringBuilder(); - zsb.Append(nullableType.Type.Equals(_reference) + zsb.Append(nullableType.Type.Equals(reference) ? "@this" : Print(nullableType.Type)); zsb.Append('?'); diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/ArithmeticOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/ArithmeticOperator.cs new file mode 100644 index 00000000..07dc2074 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/ArithmeticOperator.cs @@ -0,0 +1,26 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct ArithmeticOperator : IOperator +{ + public IReadOnlyList Values => ["+", "-", "*", "/", "%"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + if (operation is + { Operator: "-", OperandTypes: [NumberType or NullableType { Type: NumberType }] } or + { + OperandTypes: + [ + NumberType or NullableType { Type: NumberType }, + NumberType or NullableType { Type: NumberType } + ] + }) + { + resultType = NumberType.Instance; + return true; + } + + resultType = "undefined"; + return false; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/ArrayConcatOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/ArrayConcatOperator.cs new file mode 100644 index 00000000..adc01dc3 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/ArrayConcatOperator.cs @@ -0,0 +1,21 @@ +using ZLinq; + +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct ArrayConcatOperator : IOperator +{ + public IReadOnlyList Values => ["++"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not [ArrayType left, ArrayType right]) + return false; + + if (default(CommutativeTypeEqualityComparer).Equals(left.Type, right.Type)) + resultType = operation.OperandTypes.AsValueEnumerable() + .FirstOrDefault(x => x is ArrayType { Type: not Any }) ?? "undefined"; + + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/ComparisonOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/ComparisonOperator.cs new file mode 100644 index 00000000..0e454679 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/ComparisonOperator.cs @@ -0,0 +1,20 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct ComparisonOperator : IOperator +{ + public IReadOnlyList Values => [">", ">=", "<", "<="]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not + [ + NumberType or NullableType { Type: NumberType }, + NumberType or NullableType { Type: NumberType } + ]) + return false; + + resultType = BooleanType.Instance; + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/ConditionalOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/ConditionalOperator.cs new file mode 100644 index 00000000..be9c2db1 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/ConditionalOperator.cs @@ -0,0 +1,26 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct ConditionalOperator : IOperator +{ + public IReadOnlyList Values => ["!", "&&", "||"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + if (operation is + { Operator: "!", OperandTypes: [BooleanType or NullableType { Type: BooleanType }] } or + { + OperandTypes: + [ + BooleanType or NullableType { Type: BooleanType }, + BooleanType or NullableType { Type: BooleanType } + ] + }) + { + resultType = BooleanType.Instance; + return true; + } + + resultType = "undefined"; + return false; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/EqualityOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/EqualityOperator.cs new file mode 100644 index 00000000..e4ca07b5 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/EqualityOperator.cs @@ -0,0 +1,19 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct EqualityOperator : IOperator +{ + public IReadOnlyList Values => ["==", "!="]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not [var left, var right]) + return false; + + if (!default(CommutativeTypeEqualityComparer).Equals(left, right)) + return false; + + resultType = BooleanType.Instance; + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/IndexOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/IndexOperator.cs new file mode 100644 index 00000000..f7cf411a --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/IndexOperator.cs @@ -0,0 +1,25 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct IndexOperator : IOperator +{ + public IReadOnlyList Values => ["[]"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not + [ + StringType or ArrayType, + NumberType or NullableType { Type: NumberType } + ]) + return false; + + resultType = operation.OperandTypes[0] switch + { + StringType => StringType.Instance, + ArrayType arrayType => arrayType.Type, + _ => "undefined" + }; + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/LengthOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/LengthOperator.cs new file mode 100644 index 00000000..0bd9448c --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/LengthOperator.cs @@ -0,0 +1,16 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct LengthOperator : IOperator +{ + public IReadOnlyList Values => ["~"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not [StringType or ArrayType]) + return false; + + resultType = NumberType.Instance; + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/RemoveFromArrayOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/RemoveFromArrayOperator.cs new file mode 100644 index 00000000..be8ab505 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/RemoveFromArrayOperator.cs @@ -0,0 +1,17 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct RemoveFromArrayOperator : IOperator +{ + public IReadOnlyList Values => ["::"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not + [ArrayType, NumberType or NullableType { Type: NumberType }]) + return false; + + resultType = "void"; + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Operators/StringConcatOperator.cs b/src/Domain/HydraScript.Domain.IR/Types/Operators/StringConcatOperator.cs new file mode 100644 index 00000000..2243fdd3 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/Operators/StringConcatOperator.cs @@ -0,0 +1,16 @@ +namespace HydraScript.Domain.IR.Types.Operators; + +internal struct StringConcatOperator : IOperator +{ + public IReadOnlyList Values => ["+", "++"]; + + public bool TryGetResultType(OperationDescriptor operation, out Type resultType) + { + resultType = "undefined"; + if (operation.OperandTypes is not [StringType, StringType]) + return false; + + resultType = StringType.Instance; + return true; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/StringType.cs b/src/Domain/HydraScript.Domain.IR/Types/StringType.cs new file mode 100644 index 00000000..f7fc30f3 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/StringType.cs @@ -0,0 +1,15 @@ +using HydraScript.Domain.IR.Types.Operators; + +namespace HydraScript.Domain.IR.Types; + +public sealed class StringType() : + Type( + "string", + [ + default(StringConcatOperator), + default(LengthOperator), + default(IndexOperator) + ]) +{ + public static readonly StringType Instance = new(); +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.IR/Types/Type.cs b/src/Domain/HydraScript.Domain.IR/Types/Type.cs index 3dd3a062..2c20c853 100644 --- a/src/Domain/HydraScript.Domain.IR/Types/Type.cs +++ b/src/Domain/HydraScript.Domain.IR/Types/Type.cs @@ -1,15 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using HydraScript.Domain.IR.Types.Operators; + namespace HydraScript.Domain.IR.Types; -public class Type : IEquatable +public class Type(string name, List? operators = null) : IEquatable { - private readonly string _name = string.Empty; + private readonly string _name = name; + private readonly Dictionary _operators = GetOperators(operators ?? []); - protected Type() + private static Dictionary GetOperators(List operators) { - } + operators.Add(default(EqualityOperator)); + Dictionary operatorsDictionary = []; + + for (var i = 0; i < operators.Count; i++) + { + var @operator = operators[i]; + for (var j = 0; j < @operator.Values.Count; j++) + operatorsDictionary[@operator.Values[j]] = @operator; + } - public Type(string name) => - _name = name; + return operatorsDictionary; + } public virtual void ResolveReference( Type reference, @@ -18,6 +30,9 @@ public virtual void ResolveReference( { } + public virtual bool TryGetOperator(string value, [MaybeNullWhen(false)] out IOperator @operator) => + _operators.TryGetValue(value, out @operator); + public virtual bool Equals(Type? obj) => obj switch { diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index c539153a..07de0211 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -6,13 +6,13 @@ - + - + - + \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/Samples/string_reversed.js b/tests/HydraScript.IntegrationTests/Samples/string_reversed.js new file mode 100644 index 00000000..d6ccd61a --- /dev/null +++ b/tests/HydraScript.IntegrationTests/Samples/string_reversed.js @@ -0,0 +1,8 @@ +let str = "string" +let i = ~str - 1 +let revStr: string +while (i >= 0) { + revStr = revStr + str[i] + i = i - 1 +} +>>> revStr \ No newline at end of file diff --git a/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs b/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs similarity index 55% rename from tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs rename to tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs index 6e6fd65a..fac4678f 100644 --- a/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs +++ b/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs @@ -3,13 +3,13 @@ namespace HydraScript.UnitTests.Application; -public class ExplicitCastValidatorTests +public class HydraScriptTypesServiceTests { - private readonly ExplicitCastValidator _explicitCastValidator = new(); + private readonly HydraScriptTypesService _typesService = new(); [Theory, MemberData(nameof(ConversionsData))] - public void IsAllowed_Always_Success(Type from, Type to, bool expected) => - _explicitCastValidator.IsAllowed(from, to).Should().Be(expected); + public void IsExplicitCastAllowed_Always_Success(Type from, Type to, bool expected) => + _typesService.IsExplicitCastAllowed(from, to).Should().Be(expected); public static TheoryData ConversionsData => new() @@ -25,4 +25,13 @@ public void IsAllowed_Always_Success(Type from, Type to, bool expected) => { new ArrayType("number"), "number", false }, { new ArrayType("number"), new NullableType("boolean"), false }, }; + + [Fact] + public void GetDefaultValueForType_NullableTypes_ReturnsNull() + { + var calculator = new HydraScriptTypesService(); + Assert.Null(calculator.GetDefaultValueForType(new NullableType(new Any()))); + Assert.Null(calculator.GetDefaultValueForType(new NullType())); + Assert.Null(calculator.GetDefaultValueForType(new ObjectType([]))); + } } \ No newline at end of file diff --git a/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs b/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs index 39d5e8ce..25d54428 100644 --- a/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs +++ b/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs @@ -1,4 +1,3 @@ -using HydraScript.Application.StaticAnalysis.Impl; using HydraScript.Domain.IR.Types; namespace HydraScript.UnitTests.Domain.IR; @@ -40,13 +39,4 @@ public void TypeWrappingTest() str = new ArrayType(str); Assert.Equal("string?[]", str.ToString()); } - - [Fact] - public void DefaultValueTest() - { - var calculator = new DefaultValueForTypeCalculator(); - Assert.Null(calculator.GetDefaultValueForType(new NullableType(new Any()))); - Assert.Null(calculator.GetDefaultValueForType(new NullType())); - Assert.Null(calculator.GetDefaultValueForType(new ObjectType([]))); - } } \ No newline at end of file