From ae1ee5ac7699875e0d2bc2f302f77d503b3eedc7 Mon Sep 17 00:00:00 2001 From: Stepami Date: Thu, 25 Dec 2025 22:08:44 +0300 Subject: [PATCH 01/16] fix type comparing --- .../Visitors/SemanticChecker.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs index b4789433..5b029e9a 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -330,6 +330,8 @@ public Type Visit(LexicalDeclaration visitable) public Type Visit(AssignmentExpression visitable) { + var typeComparer = default(CommutativeTypeEqualityComparer); + if (visitable.Destination is CallExpression) throw new WrongAssignmentTarget(visitable.Destination); @@ -337,7 +339,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, @@ -354,7 +356,7 @@ public Type Visit(AssignmentExpression visitable) 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, From 9c32cf50137cb70532f2ad4d9e99078d8f940d33 Mon Sep 17 00:00:00 2001 From: Stepami Date: Thu, 25 Dec 2025 22:08:56 +0300 Subject: [PATCH 02/16] update packages --- Directory.Packages.props | 14 +++++++------- tests/Directory.Packages.props | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) 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/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 From 0dc203dfe92d0f19cd12c5c64ea41d81337ece3f Mon Sep 17 00:00:00 2001 From: Stepami Date: Thu, 25 Dec 2025 22:53:08 +0300 Subject: [PATCH 03/16] fix 2 --- .../Visitors/SemanticChecker.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs index 5b029e9a..4027b1ff 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -238,7 +238,7 @@ public Type Visit(BinaryExpression visitable) var lType = visitable.Left.Accept(This); var rType = visitable.Right.Accept(This); - if (visitable.Operator != "::" && !lType.Equals(rType)) + if (visitable.Operator != "::" && !default(CommutativeTypeEqualityComparer).Equals(lType, rType)) throw new IncompatibleTypesOfOperands( visitable.Segment, left: lType, @@ -305,7 +305,8 @@ public Type Visit(LexicalDeclaration visitable) throw new CannotDefineType(assignment.Source.Segment); if (sourceType.Equals("void")) throw new CannotAssignVoid(assignment.Source.Segment); - if (!registeredSymbol.Type.Equals(undefined) && !registeredSymbol.Type.Equals(sourceType)) + if (!registeredSymbol.Type.Equals(undefined) && + !default(CommutativeTypeEqualityComparer).Equals(registeredSymbol.Type, sourceType)) throw new IncompatibleTypesOfOperands( assignment.Segment, left: registeredSymbol.Type, From c90a24f9f59f5c0f22410178df624359ca948768 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 12:51:30 +0300 Subject: [PATCH 04/16] #203 - backend support --- .../Impl/Instructions/WithAssignment/Simple.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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") }; } From d322415dcb651950f3049054c56a9177313cefdc Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 12:56:20 +0300 Subject: [PATCH 05/16] #203 - central type management --- .../IJavaScriptTypesProvider.cs | 12 ++ .../Impl/DefaultValueForTypeCalculator.cs | 12 +- .../Impl/ExplicitCastValidator.cs | 13 +- .../Impl/JavaScriptTypesProvider.cs | 35 ++++-- .../Visitors/DeclarationVisitor.cs | 11 +- .../Visitors/SemanticChecker.cs | 115 ++++++++---------- .../Application/ExplicitCastValidatorTests.cs | 2 +- .../Domain/IR/TypeTests.cs | 2 +- 8 files changed, 110 insertions(+), 92 deletions(-) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs index 42e79dac..4d7ea355 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs @@ -2,6 +2,18 @@ namespace HydraScript.Application.StaticAnalysis; public interface IJavaScriptTypesProvider { + public Type Number { get; } + + public Type Boolean { get; } + + public Type String { get; } + + public Type Null { get; } + + public Type Undefined { get; } + + public Type Void { get; } + public IEnumerable GetDefaultTypes(); public bool Contains(Type type); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs index 3812ba6d..553b1abf 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs @@ -2,21 +2,21 @@ namespace HydraScript.Application.StaticAnalysis.Impl; -internal class DefaultValueForTypeCalculator : IDefaultValueForTypeCalculator +internal class DefaultValueForTypeCalculator(IJavaScriptTypesProvider typesProvider) : IDefaultValueForTypeCalculator { public object? GetDefaultValueForType(Type type) { if (type is NullableType) return null; - if (type.Equals("boolean")) + if (type.Equals(typesProvider.Boolean)) return false; - if (type.Equals("number")) + if (type.Equals(typesProvider.Number)) return 0; - if (type.Equals("string")) + if (type.Equals(typesProvider.String)) return string.Empty; - if (type.Equals("void")) + if (type.Equals(typesProvider.Void)) return new object(); - if (type.Equals(new NullType())) + if (type.Equals(typesProvider.Null)) return null; if (type is ArrayType) return new List(); diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs index 8e581ddc..4dc2f4c6 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs @@ -2,18 +2,13 @@ namespace HydraScript.Application.StaticAnalysis.Impl; -internal sealed class ExplicitCastValidator : IExplicitCastValidator +internal sealed class ExplicitCastValidator(IJavaScriptTypesProvider typesProvider) : 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] }, + { typesProvider.String, [new Any()] }, + { typesProvider.Number, [typesProvider.String, typesProvider.Boolean] }, + { typesProvider.Boolean, [typesProvider.String, typesProvider.Number] }, }; public bool IsAllowed(Type from, Type to) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs index 425a08a5..a460ee71 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs @@ -4,15 +4,32 @@ namespace HydraScript.Application.StaticAnalysis.Impl; internal class JavaScriptTypesProvider : IJavaScriptTypesProvider { - private readonly HashSet _types = - [ - "number", - "boolean", - "string", - new NullType(), - "undefined", - "void" - ]; + private readonly HashSet _types; + + public JavaScriptTypesProvider() + { + _types = + [ + Number, + Boolean, + String, + Null, + Undefined, + Void + ]; + } + + public Type Number { get; } = "number"; + + public Type Boolean { get; } = "boolean"; + + public Type String { get; } = new StringType(); + + public Type Null { get; } = new NullType(); + + public Type Undefined { get; } = "undefined"; + + public Type Void { get; } = "void"; public IEnumerable GetDefaultTypes() => _types; diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs index 37308f5d..5dcb04f5 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 IJavaScriptTypesProvider _typesProvider; 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( + IJavaScriptTypesProvider typesProvider, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, @@ -29,6 +31,7 @@ public DeclarationVisitor( IVisitor typeBuilder, IVisitor returnAnalyzer) { + _typesProvider = typesProvider; _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) ?? _typesProvider.Undefined; - if (destinationType == "undefined" && + if (destinationType == _typesProvider.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(_typesProvider.Undefined)) { if (visitable.HasReturnStatement) _functionStorage.Save(functionSymbol, visitable); else - functionSymbol.DefineReturnType("void"); + functionSymbol.DefineReturnType(_typesProvider.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 4027b1ff..7d152a85 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -45,6 +45,7 @@ internal class SemanticChecker : VisitorBase, IVisitor, IVisitor { + private readonly IJavaScriptTypesProvider _typesProvider; private readonly IDefaultValueForTypeCalculator _calculator; private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; @@ -55,6 +56,7 @@ internal class SemanticChecker : VisitorBase, private readonly IVisitor _typeBuilder; public SemanticChecker( + IJavaScriptTypesProvider typesProvider, IDefaultValueForTypeCalculator calculator, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, @@ -64,6 +66,7 @@ public SemanticChecker( IExplicitCastValidator explicitCastValidator, IVisitor typeBuilder) { + _typesProvider = typesProvider; _calculator = calculator; _functionStorage = functionStorage; _methodStorage = methodStorage; @@ -74,7 +77,7 @@ public SemanticChecker( _typeBuilder = typeBuilder; } - public override Type Visit(IAbstractSyntaxTreeNode visitable) => "undefined"; + public override Type Visit(IAbstractSyntaxTreeNode visitable) => _typesProvider.Undefined; public Type Visit(ScriptBody visitable) { @@ -89,30 +92,30 @@ public Type Visit(ScriptBody visitable) _computedTypes.Clear(); _ambiguousInvocations.Clear(); - return "undefined"; + return _typesProvider.Undefined; } public Type Visit(WhileStatement visitable) { var condType = visitable.Condition.Accept(This); - if (!condType.Equals("boolean")) + if (!condType.Equals(_typesProvider.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, condType); visitable.Statement.Accept(This); - return "undefined"; + return _typesProvider.Undefined; } public Type Visit(IfStatement visitable) { var testType = visitable.Test.Accept(This); - if (!testType.Equals("boolean")) + if (!testType.Equals(_typesProvider.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, testType); visitable.Then.Accept(This); visitable.Else?.Accept(This); - return "undefined"; + return _typesProvider.Undefined; } public Type Visit(InsideStatementJump visitable) @@ -135,7 +138,7 @@ public Type Visit(InsideStatementJump visitable) break; } - return "undefined"; + return _typesProvider.Undefined; } public Type Visit(ReturnStatement visitable) @@ -143,7 +146,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) ?? _typesProvider.Void; } public Type Visit(ExpressionStatement visitable) => @@ -157,7 +160,7 @@ public Type Visit(IdentifierReference visitable) return symbol?.Type ?? throw new UnknownIdentifierReference(visitable); } - public Type Visit(EnvVarReference visitable) => "string"; + public Type Visit(EnvVarReference visitable) => _typesProvider.String; public Type Visit(Literal visitable) => visitable.Type.Accept(_typeBuilder); @@ -217,7 +220,7 @@ public ObjectType Visit(ObjectLiteral visitable) public Type Visit(ConditionalExpression visitable) { var tType = visitable.Test.Accept(This); - if (!tType.Equals("boolean")) + if (!tType.Equals(_typesProvider.Boolean)) throw new NotBooleanTestExpression(visitable.Test.Segment, tType); var cType = visitable.Consequent.Accept(This); @@ -244,34 +247,30 @@ public Type Visit(BinaryExpression visitable) left: lType, right: rType); - Type number = "number"; - Type @string = "string"; - Type boolean = "boolean"; - return visitable.Operator switch { - "+" when lType.Equals(number) => number, - "+" when lType.Equals(@string) => @string, + "+" when lType.Equals(_typesProvider.Number) => _typesProvider.Number, + "+" when lType.Equals(_typesProvider.String) => _typesProvider.String, "+" => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "-" or "*" or "/" or "%" => lType.Equals(number) - ? number + "-" or "*" or "/" or "%" => lType.Equals(_typesProvider.Number) + ? _typesProvider.Number : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "||" or "&&" => lType.Equals(boolean) - ? boolean + "||" or "&&" => lType.Equals(_typesProvider.Boolean) + ? _typesProvider.Boolean : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "==" or "!=" => boolean, - ">" or ">=" or "<" or "<=" => lType.Equals(number) - ? boolean + "==" or "!=" => _typesProvider.Boolean, + ">" or ">=" or "<" or "<=" => lType.Equals(_typesProvider.Number) + ? _typesProvider.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 => + "::" when lType is StringType or not ArrayType => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" => rType.Equals(number) ? "void" : throw new ArrayAccessException(visitable.Segment, rType), - _ => "undefined" + "::" => rType.Equals(_typesProvider.Number) ? _typesProvider.Void : throw new ArrayAccessException(visitable.Segment, rType), + _ => _typesProvider.Undefined }; } @@ -279,42 +278,37 @@ public Type Visit(UnaryExpression visitable) { var eType = visitable.Expression.Accept(This); - Type number = "number"; - Type boolean = "boolean"; - return visitable.Operator switch { - "-" when eType.Equals(number) => number, - "!" when eType.Equals(boolean) => boolean, - "~" when eType is ArrayType => number, + "-" when eType.Equals(_typesProvider.Number) => _typesProvider.Number, + "!" when eType.Equals(_typesProvider.Boolean) => _typesProvider.Boolean, + "~" when eType is ArrayType => _typesProvider.Number, _ => throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator) }; } 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(_typesProvider.Undefined)) throw new CannotDefineType(assignment.Source.Segment); - if (sourceType.Equals("void")) + if (sourceType.Equals(_typesProvider.Void)) throw new CannotAssignVoid(assignment.Source.Segment); - if (!registeredSymbol.Type.Equals(undefined) && + if (!registeredSymbol.Type.Equals(_typesProvider.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(_typesProvider.Undefined)) throw new CannotAssignNullWhenUndefined(assignment.Segment); - var actualType = registeredSymbol.Type.Equals(undefined) + var actualType = registeredSymbol.Type.Equals(_typesProvider.Undefined) ? sourceType : registeredSymbol.Type; var actualSymbol = actualType switch @@ -326,7 +320,7 @@ public Type Visit(LexicalDeclaration visitable) _symbolTables[visitable.Scope].AddSymbol(actualSymbol); } - return undefined; + return _typesProvider.Undefined; } public Type Visit(AssignmentExpression visitable) @@ -352,7 +346,7 @@ 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, _typesProvider.String); if (symbol.ReadOnly) throw new AssignmentToConst(visitable.Destination.Id); @@ -371,7 +365,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) ?? _typesProvider.Undefined; } public Type Visit(IndexAccess visitable) @@ -385,12 +379,12 @@ public Type Visit(IndexAccess visitable) throw new NonAccessibleType(prevType); var indexType = visitable.Index.Accept(This); - if (!indexType.Equals("number")) + if (!indexType.Equals(_typesProvider.Number)) 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) ?? _typesProvider.Undefined : elemType; } public Type Visit(DotAccess visitable) @@ -410,7 +404,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) ?? _typesProvider.Undefined : fieldType; } public ObjectType Visit(WithExpression visitable) @@ -438,15 +432,15 @@ public Type Visit(CastAsExpression visitable) { var from = visitable.Expression.Accept(This); - if (from.Equals("undefined")) + if (from.Equals(_typesProvider.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(_typesProvider.String) => CastAsExpression.DestinationType.String, + _ when to.Equals(_typesProvider.Number) => CastAsExpression.DestinationType.Number, + _ when to.Equals(_typesProvider.Boolean) => CastAsExpression.DestinationType.Boolean, _ => CastAsExpression.DestinationType.Undefined }; @@ -493,14 +487,13 @@ public Type Visit(CallExpression visitable) throw new WrongTypeOfArgument(expr.Segment, expectedType, actualType); }); - if (functionSymbol.Type.Equals("undefined")) + if (functionSymbol.Type.Equals(_typesProvider.Undefined)) { var declaration = _functionStorage.Get(functionSymbol); functionReturnType = declaration.Accept(This); } - Type @void = "void"; - if (!functionReturnType.Equals(@void)) + if (!functionReturnType.Equals(_typesProvider.Void)) visitable.HasReturnValue = true; return functionReturnType; } @@ -512,26 +505,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(_typesProvider.Undefined)) throw new CannotDefineType(visitable.Segment); - if (!symbol.Type.Equals(undefined) && !symbol.Type.Equals(returnStatementType)) + if (!symbol.Type.Equals(_typesProvider.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(_typesProvider.Undefined)) symbol.DefineReturnType(returnTypes.Single()); - Type @void = "void"; - if (!symbol.Type.Equals(@void) && !visitable.AllCodePathsEndedWithReturn) + if (!symbol.Type.Equals(_typesProvider.Void) && !visitable.AllCodePathsEndedWithReturn) throw new FunctionWithoutReturnStatement(visitable.Segment); if (symbol.Type is NullType) @@ -544,21 +535,21 @@ public Type Visit(BlockStatement visitable) { for (var i = 0; i < visitable.Count; i++) visitable[i].Accept(This); - return "undefined"; + return _typesProvider.Undefined; } public Type Visit(OutputStatement visitable) { visitable.Expression.Accept(This); - return "undefined"; + return _typesProvider.Undefined; } public Type Visit(InputStatement visitable) { IAbstractSyntaxTreeNode id = visitable.Destination; var idType = id.Accept(This); - if (!idType.Equals("string")) + if (!idType.Equals(_typesProvider.String)) throw new UnsupportedOperation(visitable.Segment, idType, "<<<"); - return "undefined"; + return _typesProvider.Undefined; } } \ No newline at end of file diff --git a/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs b/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs index 6e6fd65a..76d9abef 100644 --- a/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs +++ b/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs @@ -5,7 +5,7 @@ namespace HydraScript.UnitTests.Application; public class ExplicitCastValidatorTests { - private readonly ExplicitCastValidator _explicitCastValidator = new(); + private readonly ExplicitCastValidator _explicitCastValidator = new(new JavaScriptTypesProvider()); [Theory, MemberData(nameof(ConversionsData))] public void IsAllowed_Always_Success(Type from, Type to, bool expected) => diff --git a/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs b/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs index 39d5e8ce..d46287e1 100644 --- a/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs +++ b/tests/HydraScript.UnitTests/Domain/IR/TypeTests.cs @@ -44,7 +44,7 @@ public void TypeWrappingTest() [Fact] public void DefaultValueTest() { - var calculator = new DefaultValueForTypeCalculator(); + var calculator = new DefaultValueForTypeCalculator(new JavaScriptTypesProvider()); Assert.Null(calculator.GetDefaultValueForType(new NullableType(new Any()))); Assert.Null(calculator.GetDefaultValueForType(new NullType())); Assert.Null(calculator.GetDefaultValueForType(new ObjectType([]))); From 26de6aacecc737a2365deb584dd1bca7f36730cf Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 12:56:32 +0300 Subject: [PATCH 06/16] #203 - string type --- .../HydraScript.Domain.IR/Types/StringType.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/Domain/HydraScript.Domain.IR/Types/StringType.cs 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..e57fbe52 --- /dev/null +++ b/src/Domain/HydraScript.Domain.IR/Types/StringType.cs @@ -0,0 +1,13 @@ +namespace HydraScript.Domain.IR.Types; + +public sealed class StringType() : ArrayType("string") +{ + public override bool Equals(Type? obj) + { + if (obj?.Equals("string") is true) + return true; + return base.Equals(obj); + } + + public override string ToString() => "string"; +} \ No newline at end of file From c3e248c257c66aae560ec3d7240e21a350b92580 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 13:11:56 +0300 Subject: [PATCH 07/16] #203 - merge type services --- .../IDefaultValueForTypeCalculator.cs | 6 - .../IExplicitCastValidator.cs | 6 - ...rovider.cs => IHydraScriptTypesService.cs} | 6 +- .../ITypeDeclarationsResolver.cs | 2 + .../Impl/DefaultValueForTypeCalculator.cs | 26 ---- .../Impl/ExplicitCastValidator.cs | 32 ----- .../Impl/HydraScriptTypesService.cs | 83 +++++++++++++ .../Impl/JavaScriptTypesProvider.cs | 37 ------ .../Impl/StandardLibraryProvider.cs | 9 +- .../Impl/TypeDeclarationsResolver.cs | 6 +- .../ServiceCollectionExtensions.cs | 4 +- .../Visitors/DeclarationVisitor.cs | 14 +-- .../Visitors/SemanticChecker.cs | 114 +++++++++--------- .../Visitors/TypeSystemLoader.cs | 5 +- ...sts.cs => HydraScriptTypesServiceTests.cs} | 15 ++- .../Domain/IR/TypeTests.cs | 10 -- 16 files changed, 171 insertions(+), 204 deletions(-) delete mode 100644 src/Application/HydraScript.Application.StaticAnalysis/IDefaultValueForTypeCalculator.cs delete mode 100644 src/Application/HydraScript.Application.StaticAnalysis/IExplicitCastValidator.cs rename src/Application/HydraScript.Application.StaticAnalysis/{IJavaScriptTypesProvider.cs => IHydraScriptTypesService.cs} (68%) delete mode 100644 src/Application/HydraScript.Application.StaticAnalysis/Impl/DefaultValueForTypeCalculator.cs delete mode 100644 src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs create mode 100644 src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs delete mode 100644 src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs rename tests/HydraScript.UnitTests/Application/{ExplicitCastValidatorTests.cs => HydraScriptTypesServiceTests.cs} (62%) 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/IJavaScriptTypesProvider.cs b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs similarity index 68% rename from src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs rename to src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs index 4d7ea355..575c0fc3 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/IJavaScriptTypesProvider.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs @@ -1,6 +1,6 @@ namespace HydraScript.Application.StaticAnalysis; -public interface IJavaScriptTypesProvider +public interface IHydraScriptTypesService { public Type Number { get; } @@ -17,4 +17,8 @@ public interface IJavaScriptTypesProvider 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/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 553b1abf..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(IJavaScriptTypesProvider typesProvider) : IDefaultValueForTypeCalculator -{ - public object? GetDefaultValueForType(Type type) - { - if (type is NullableType) - return null; - if (type.Equals(typesProvider.Boolean)) - return false; - if (type.Equals(typesProvider.Number)) - return 0; - if (type.Equals(typesProvider.String)) - return string.Empty; - if (type.Equals(typesProvider.Void)) - return new object(); - if (type.Equals(typesProvider.Null)) - 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 4dc2f4c6..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ExplicitCastValidator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal sealed class ExplicitCastValidator(IJavaScriptTypesProvider typesProvider) : IExplicitCastValidator -{ - private readonly Dictionary> _allowedConversions = new() - { - { typesProvider.String, [new Any()] }, - { typesProvider.Number, [typesProvider.String, typesProvider.Boolean] }, - { typesProvider.Boolean, [typesProvider.String, typesProvider.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..fc808864 --- /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 { get; } = "number"; + + public Type Boolean { get; } = "boolean"; + + public Type String { get; } = new StringType(); + + public Type Null { get; } = new NullType(); + + public Type Undefined { get; } = "undefined"; + + public Type Void { get; } = "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 a460ee71..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/JavaScriptTypesProvider.cs +++ /dev/null @@ -1,37 +0,0 @@ -using HydraScript.Domain.IR.Types; - -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal class JavaScriptTypesProvider : IJavaScriptTypesProvider -{ - private readonly HashSet _types; - - public JavaScriptTypesProvider() - { - _types = - [ - Number, - Boolean, - String, - Null, - Undefined, - Void - ]; - } - - public Type Number { get; } = "number"; - - public Type Boolean { get; } = "boolean"; - - public Type String { get; } = new StringType(); - - public Type Null { get; } = new NullType(); - - public Type Undefined { get; } = "undefined"; - - public Type Void { get; } = "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 5dcb04f5..7bb0f789 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs @@ -14,7 +14,7 @@ internal class DeclarationVisitor : VisitorNoReturnBase IVisitor, IVisitor { - private readonly IJavaScriptTypesProvider _typesProvider; + private readonly IHydraScriptTypesService _typesService; private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; private readonly ISymbolTableStorage _symbolTables; @@ -23,7 +23,7 @@ internal class DeclarationVisitor : VisitorNoReturnBase private readonly IVisitor _returnAnalyzer; public DeclarationVisitor( - IJavaScriptTypesProvider typesProvider, + IHydraScriptTypesService typesService, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, @@ -31,7 +31,7 @@ public DeclarationVisitor( IVisitor typeBuilder, IVisitor returnAnalyzer) { - _typesProvider = typesProvider; + _typesService = typesService; _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; @@ -57,9 +57,9 @@ public VisitUnit Visit(LexicalDeclaration visitable) throw new DeclarationAlreadyExists(assignment.Destination.Id); var destinationType = assignment.DestinationType?.Accept( - _typeBuilder) ?? _typesProvider.Undefined; + _typeBuilder) ?? _typesService.Undefined; - if (destinationType == _typesProvider.Undefined && + if (destinationType == _typesService.Undefined && assignment.Source is ImplicitLiteral or ArrayLiteral { Expressions.Count: 0 }) throw visitable.ReadOnly ? new ConstWithoutInitializer(assignment.Destination.Id) @@ -104,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(_typesProvider.Undefined)) + if (functionSymbol.Type.Equals(_typesService.Undefined)) { if (visitable.HasReturnStatement) _functionStorage.Save(functionSymbol, visitable); else - functionSymbol.DefineReturnType(_typesProvider.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 7d152a85..946d1dee 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -45,39 +45,33 @@ internal class SemanticChecker : VisitorBase, IVisitor, IVisitor { - private readonly IJavaScriptTypesProvider _typesProvider; - 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( - IJavaScriptTypesProvider typesProvider, - IDefaultValueForTypeCalculator calculator, + IHydraScriptTypesService typesService, IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, IComputedTypesStorage computedTypes, IAmbiguousInvocationStorage ambiguousInvocations, - IExplicitCastValidator explicitCastValidator, IVisitor typeBuilder) { - _typesProvider = typesProvider; - _calculator = calculator; + _typesService = typesService; _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; _computedTypes = computedTypes; _ambiguousInvocations = ambiguousInvocations; - _explicitCastValidator = explicitCastValidator; _typeBuilder = typeBuilder; } - public override Type Visit(IAbstractSyntaxTreeNode visitable) => _typesProvider.Undefined; + public override Type Visit(IAbstractSyntaxTreeNode visitable) => _typesService.Undefined; public Type Visit(ScriptBody visitable) { @@ -92,30 +86,30 @@ public Type Visit(ScriptBody visitable) _computedTypes.Clear(); _ambiguousInvocations.Clear(); - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(WhileStatement visitable) { var condType = visitable.Condition.Accept(This); - if (!condType.Equals(_typesProvider.Boolean)) + if (!condType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, condType); visitable.Statement.Accept(This); - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(IfStatement visitable) { var testType = visitable.Test.Accept(This); - if (!testType.Equals(_typesProvider.Boolean)) + if (!testType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Segment, testType); visitable.Then.Accept(This); visitable.Else?.Accept(This); - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(InsideStatementJump visitable) @@ -138,7 +132,7 @@ public Type Visit(InsideStatementJump visitable) break; } - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(ReturnStatement visitable) @@ -146,7 +140,7 @@ public Type Visit(ReturnStatement visitable) if (!visitable.ChildOf()) throw new ReturnOutsideFunction(visitable.Segment); - return visitable.Expression?.Accept(This) ?? _typesProvider.Void; + return visitable.Expression?.Accept(This) ?? _typesService.Void; } public Type Visit(ExpressionStatement visitable) => @@ -160,7 +154,7 @@ public Type Visit(IdentifierReference visitable) return symbol?.Type ?? throw new UnknownIdentifierReference(visitable); } - public Type Visit(EnvVarReference visitable) => _typesProvider.String; + public Type Visit(EnvVarReference visitable) => _typesService.String; public Type Visit(Literal visitable) => visitable.Type.Accept(_typeBuilder); @@ -170,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); } @@ -220,7 +214,7 @@ public ObjectType Visit(ObjectLiteral visitable) public Type Visit(ConditionalExpression visitable) { var tType = visitable.Test.Accept(This); - if (!tType.Equals(_typesProvider.Boolean)) + if (!tType.Equals(_typesService.Boolean)) throw new NotBooleanTestExpression(visitable.Test.Segment, tType); var cType = visitable.Consequent.Accept(This); @@ -249,18 +243,18 @@ public Type Visit(BinaryExpression visitable) return visitable.Operator switch { - "+" when lType.Equals(_typesProvider.Number) => _typesProvider.Number, - "+" when lType.Equals(_typesProvider.String) => _typesProvider.String, + "+" when lType.Equals(_typesService.Number) => _typesService.Number, + "+" when lType.Equals(_typesService.String) => _typesService.String, "+" => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "-" or "*" or "/" or "%" => lType.Equals(_typesProvider.Number) - ? _typesProvider.Number + "-" or "*" or "/" or "%" => lType.Equals(_typesService.Number) + ? _typesService.Number : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "||" or "&&" => lType.Equals(_typesProvider.Boolean) - ? _typesProvider.Boolean + "||" or "&&" => lType.Equals(_typesService.Boolean) + ? _typesService.Boolean : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "==" or "!=" => _typesProvider.Boolean, - ">" or ">=" or "<" or "<=" => lType.Equals(_typesProvider.Number) - ? _typesProvider.Boolean + "==" or "!=" => _typesService.Boolean, + ">" or ">=" or "<" or "<=" => lType.Equals(_typesService.Number) + ? _typesService.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), @@ -269,8 +263,8 @@ public Type Visit(BinaryExpression visitable) : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), "::" when lType is StringType or not ArrayType => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" => rType.Equals(_typesProvider.Number) ? _typesProvider.Void : throw new ArrayAccessException(visitable.Segment, rType), - _ => _typesProvider.Undefined + "::" => rType.Equals(_typesService.Number) ? _typesService.Void : throw new ArrayAccessException(visitable.Segment, rType), + _ => _typesService.Undefined }; } @@ -280,9 +274,9 @@ public Type Visit(UnaryExpression visitable) return visitable.Operator switch { - "-" when eType.Equals(_typesProvider.Number) => _typesProvider.Number, - "!" when eType.Equals(_typesProvider.Boolean) => _typesProvider.Boolean, - "~" when eType is ArrayType => _typesProvider.Number, + "-" when eType.Equals(_typesService.Number) => _typesService.Number, + "!" when eType.Equals(_typesService.Boolean) => _typesService.Boolean, + "~" when eType is ArrayType => _typesService.Number, _ => throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator) }; } @@ -295,20 +289,20 @@ public Type Visit(LexicalDeclaration visitable) var registeredSymbol = _symbolTables[visitable.Scope].FindSymbol(new VariableSymbolId(assignment.Destination.Id))!; var sourceType = assignment.Source.Accept(This); - if (sourceType.Equals(_typesProvider.Undefined)) + if (sourceType.Equals(_typesService.Undefined)) throw new CannotDefineType(assignment.Source.Segment); - if (sourceType.Equals(_typesProvider.Void)) + if (sourceType.Equals(_typesService.Void)) throw new CannotAssignVoid(assignment.Source.Segment); - if (!registeredSymbol.Type.Equals(_typesProvider.Undefined) && + 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(_typesProvider.Undefined)) + if (sourceType is NullType && registeredSymbol.Type.Equals(_typesService.Undefined)) throw new CannotAssignNullWhenUndefined(assignment.Segment); - var actualType = registeredSymbol.Type.Equals(_typesProvider.Undefined) + var actualType = registeredSymbol.Type.Equals(_typesService.Undefined) ? sourceType : registeredSymbol.Type; var actualSymbol = actualType switch @@ -320,7 +314,7 @@ public Type Visit(LexicalDeclaration visitable) _symbolTables[visitable.Scope].AddSymbol(actualSymbol); } - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(AssignmentExpression visitable) @@ -346,7 +340,7 @@ 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, _typesProvider.String); + : new VariableSymbol(visitable.Destination.Id, _typesService.String); if (symbol.ReadOnly) throw new AssignmentToConst(visitable.Destination.Id); @@ -365,7 +359,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) ?? _typesProvider.Undefined; + return visitable.Empty() ? idType : visitable.AccessChain?.Accept(This) ?? _typesService.Undefined; } public Type Visit(IndexAccess visitable) @@ -379,12 +373,12 @@ public Type Visit(IndexAccess visitable) throw new NonAccessibleType(prevType); var indexType = visitable.Index.Accept(This); - if (!indexType.Equals(_typesProvider.Number)) + if (!indexType.Equals(_typesService.Number)) throw new ArrayAccessException(visitable.Segment, indexType); var elemType = arrayType.Type; visitable.ComputedTypeGuid = _computedTypes.Save(elemType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesProvider.Undefined : elemType; + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : elemType; } public Type Visit(DotAccess visitable) @@ -404,7 +398,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) ?? _typesProvider.Undefined : fieldType; + return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : fieldType; } public ObjectType Visit(WithExpression visitable) @@ -432,19 +426,19 @@ public Type Visit(CastAsExpression visitable) { var from = visitable.Expression.Accept(This); - if (from.Equals(_typesProvider.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(_typesProvider.String) => CastAsExpression.DestinationType.String, - _ when to.Equals(_typesProvider.Number) => CastAsExpression.DestinationType.Number, - _ when to.Equals(_typesProvider.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); } @@ -487,13 +481,13 @@ public Type Visit(CallExpression visitable) throw new WrongTypeOfArgument(expr.Segment, expectedType, actualType); }); - if (functionSymbol.Type.Equals(_typesProvider.Undefined)) + if (functionSymbol.Type.Equals(_typesService.Undefined)) { var declaration = _functionStorage.Get(functionSymbol); functionReturnType = declaration.Accept(This); } - if (!functionReturnType.Equals(_typesProvider.Void)) + if (!functionReturnType.Equals(_typesService.Void)) visitable.HasReturnValue = true; return functionReturnType; } @@ -510,19 +504,19 @@ public Type Visit(FunctionDeclaration visitable) { var returnStatementType = visitable.ReturnStatements[i].Accept(This); returnTypes.Add(returnStatementType); - if (returnTypes.Count > 1 && symbol.Type.Equals(_typesProvider.Undefined)) + if (returnTypes.Count > 1 && symbol.Type.Equals(_typesService.Undefined)) throw new CannotDefineType(visitable.Segment); - if (!symbol.Type.Equals(_typesProvider.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(_typesProvider.Undefined)) + if (symbol.Type.Equals(_typesService.Undefined)) symbol.DefineReturnType(returnTypes.Single()); - if (!symbol.Type.Equals(_typesProvider.Void) && !visitable.AllCodePathsEndedWithReturn) + if (!symbol.Type.Equals(_typesService.Void) && !visitable.AllCodePathsEndedWithReturn) throw new FunctionWithoutReturnStatement(visitable.Segment); if (symbol.Type is NullType) @@ -535,21 +529,21 @@ public Type Visit(BlockStatement visitable) { for (var i = 0; i < visitable.Count; i++) visitable[i].Accept(This); - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(OutputStatement visitable) { visitable.Expression.Accept(This); - return _typesProvider.Undefined; + return _typesService.Undefined; } public Type Visit(InputStatement visitable) { IAbstractSyntaxTreeNode id = visitable.Destination; var idType = id.Accept(This); - if (!idType.Equals(_typesProvider.String)) + if (!idType.Equals(_typesService.String)) throw new UnsupportedOperation(visitable.Segment, idType, "<<<"); - return _typesProvider.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/tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs b/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs similarity index 62% rename from tests/HydraScript.UnitTests/Application/ExplicitCastValidatorTests.cs rename to tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs index 76d9abef..d73dcbde 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(new JavaScriptTypesProvider()); + 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); + _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 DefaultValueTest() + { + 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 d46287e1..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(new JavaScriptTypesProvider()); - 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 From 86e0835da5c6f65feb9adf7967e19757cac5baff Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 13:14:49 +0300 Subject: [PATCH 08/16] fix test names --- .../Application/HydraScriptTypesServiceTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs b/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs index d73dcbde..fac4678f 100644 --- a/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs +++ b/tests/HydraScript.UnitTests/Application/HydraScriptTypesServiceTests.cs @@ -8,7 +8,7 @@ public class HydraScriptTypesServiceTests private readonly HydraScriptTypesService _typesService = new(); [Theory, MemberData(nameof(ConversionsData))] - public void IsAllowed_Always_Success(Type from, Type to, bool expected) => + public void IsExplicitCastAllowed_Always_Success(Type from, Type to, bool expected) => _typesService.IsExplicitCastAllowed(from, to).Should().Be(expected); public static TheoryData ConversionsData => @@ -27,7 +27,7 @@ public void IsAllowed_Always_Success(Type from, Type to, bool expected) => }; [Fact] - public void DefaultValueTest() + public void GetDefaultValueForType_NullableTypes_ReturnsNull() { var calculator = new HydraScriptTypesService(); Assert.Null(calculator.GetDefaultValueForType(new NullableType(new Any()))); From 8938b68aee272ac4ed6376fbc582009477b04ab0 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:04:35 +0300 Subject: [PATCH 09/16] #203 - operators --- .../HydraScript.Domain.IR/Types/IOperator.cs | 12 +++++++++ .../Types/Operators/ArithmeticOperator.cs | 26 +++++++++++++++++++ .../Types/Operators/ArrayConcatOperator.cs | 21 +++++++++++++++ .../Types/Operators/ComparisonOperator.cs | 20 ++++++++++++++ .../Types/Operators/ConditionalOperator.cs | 26 +++++++++++++++++++ .../Types/Operators/EqualityOperator.cs | 19 ++++++++++++++ .../Types/Operators/IndexOperator.cs | 25 ++++++++++++++++++ .../Types/Operators/LengthOperator.cs | 16 ++++++++++++ .../Operators/RemoveFromArrayOperator.cs | 17 ++++++++++++ .../Types/Operators/StringConcatOperator.cs | 16 ++++++++++++ 10 files changed, 198 insertions(+) create mode 100644 src/Domain/HydraScript.Domain.IR/Types/IOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/ArithmeticOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/ArrayConcatOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/ComparisonOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/ConditionalOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/EqualityOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/IndexOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/LengthOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/RemoveFromArrayOperator.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/Operators/StringConcatOperator.cs 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/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 From a452a0caef40d2e4fc8787f7113b56a9c138a1b6 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:04:53 +0300 Subject: [PATCH 10/16] #203 - zlinq to ir --- src/Domain/HydraScript.Domain.IR/HydraScript.Domain.IR.csproj | 1 + 1 file changed, 1 insertion(+) 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 @@  + From 8702f4b667ee18f0e963774e9910a8adcad11784 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:05:11 +0300 Subject: [PATCH 11/16] #203 - default types --- .../Types/BooleanType.cs | 8 ++++++++ .../HydraScript.Domain.IR/Types/NumberType.cs | 14 +++++++++++++ .../HydraScript.Domain.IR/Types/StringType.cs | 20 ++++++++++--------- 3 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 src/Domain/HydraScript.Domain.IR/Types/BooleanType.cs create mode 100644 src/Domain/HydraScript.Domain.IR/Types/NumberType.cs 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/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/StringType.cs b/src/Domain/HydraScript.Domain.IR/Types/StringType.cs index e57fbe52..f7fc30f3 100644 --- a/src/Domain/HydraScript.Domain.IR/Types/StringType.cs +++ b/src/Domain/HydraScript.Domain.IR/Types/StringType.cs @@ -1,13 +1,15 @@ +using HydraScript.Domain.IR.Types.Operators; + namespace HydraScript.Domain.IR.Types; -public sealed class StringType() : ArrayType("string") +public sealed class StringType() : + Type( + "string", + [ + default(StringConcatOperator), + default(LengthOperator), + default(IndexOperator) + ]) { - public override bool Equals(Type? obj) - { - if (obj?.Equals("string") is true) - return true; - return base.Equals(obj); - } - - public override string ToString() => "string"; + public static readonly StringType Instance = new(); } \ No newline at end of file From 988b3b722d3141966c35a61aafe7d4adaf425af7 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:05:28 +0300 Subject: [PATCH 12/16] #203 - operator types refactoring --- .../HydraScript.Domain.IR/Types/ArrayType.cs | 12 ++++++- .../HydraScript.Domain.IR/Types/NullType.cs | 4 ++- .../Types/NullableType.cs | 5 +++ .../HydraScript.Domain.IR/Types/ObjectType.cs | 35 +++++++------------ .../HydraScript.Domain.IR/Types/Type.cs | 27 ++++++++++---- 5 files changed, 52 insertions(+), 31 deletions(-) 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/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/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/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 { From ef13e71df977b9c0c7c65d94953c9092fb5c2cae Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:05:48 +0300 Subject: [PATCH 13/16] #203 - operator application to static analysis --- .../IHydraScriptTypesService.cs | 2 - .../Impl/HydraScriptTypesService.cs | 12 ++-- .../Visitors/SemanticChecker.cs | 59 ++++++++----------- 3 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs index 575c0fc3..cabbd2d3 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/IHydraScriptTypesService.cs @@ -8,8 +8,6 @@ public interface IHydraScriptTypesService public Type String { get; } - public Type Null { get; } - public Type Undefined { get; } public Type Void { get; } diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs index fc808864..013ace91 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Impl/HydraScriptTypesService.cs @@ -26,17 +26,17 @@ public HydraScriptTypesService() }; } - public Type Number { get; } = "number"; + public Type Number => NumberType.Instance; - public Type Boolean { get; } = "boolean"; + public Type Boolean => BooleanType.Instance; - public Type String { get; } = new StringType(); + public Type String => StringType.Instance; - public Type Null { get; } = new NullType(); + private Type Null => NullType.Instance; - public Type Undefined { get; } = "undefined"; + public Type Undefined => "undefined"; - public Type Void { get; } = "void"; + public Type Void => "void"; public IEnumerable GetDefaultTypes() => _types; diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs index 946d1dee..fa217df2 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -235,50 +235,37 @@ public Type Visit(BinaryExpression visitable) var lType = visitable.Left.Accept(This); var rType = visitable.Right.Accept(This); - if (visitable.Operator != "::" && !default(CommutativeTypeEqualityComparer).Equals(lType, 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); - return visitable.Operator switch - { - "+" when lType.Equals(_typesService.Number) => _typesService.Number, - "+" when lType.Equals(_typesService.String) => _typesService.String, - "+" => throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "-" or "*" or "/" or "%" => lType.Equals(_typesService.Number) - ? _typesService.Number - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "||" or "&&" => lType.Equals(_typesService.Boolean) - ? _typesService.Boolean - : throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "==" or "!=" => _typesService.Boolean, - ">" or ">=" or "<" or "<=" => lType.Equals(_typesService.Number) - ? _typesService.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 StringType or not ArrayType => - throw new UnsupportedOperation(visitable.Segment, lType, visitable.Operator), - "::" => rType.Equals(_typesService.Number) ? _typesService.Void : throw new ArrayAccessException(visitable.Segment, rType), - _ => _typesService.Undefined - }; + if (resultType.Equals(_typesService.Undefined)) + throw new CannotDefineType(visitable.Segment); + + return resultType; } public Type Visit(UnaryExpression visitable) { var eType = visitable.Expression.Accept(This); - return visitable.Operator switch - { - "-" when eType.Equals(_typesService.Number) => _typesService.Number, - "!" when eType.Equals(_typesService.Boolean) => _typesService.Boolean, - "~" when eType is ArrayType => _typesService.Number, - _ => throw new UnsupportedOperation(visitable.Segment, eType, visitable.Operator) - }; + if (!eType.TryGetOperator(visitable.Operator, out var @operator)) + 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) @@ -369,14 +356,14 @@ 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(_typesService.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) ?? _typesService.Undefined : elemType; } From 80b63a9ec1496fba967d90ee1505fc60b9f2dfae Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:10:51 +0300 Subject: [PATCH 14/16] #203 - tests --- .../Samples/string_reversed.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/HydraScript.IntegrationTests/Samples/string_reversed.js 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 From 933df1852de6764bd60ca1562ef8f0c2a9c199ba Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 17:31:31 +0300 Subject: [PATCH 15/16] docs --- Readme.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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 From 2e8f7540ae3bccc5dd0fb6522cc1d535e2547e70 Mon Sep 17 00:00:00 2001 From: Stepami Date: Fri, 26 Dec 2025 18:00:36 +0300 Subject: [PATCH 16/16] fix release --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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