diff --git a/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs b/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs index a34b5ea0..7d1f8aaa 100644 --- a/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs +++ b/src/Application/HydraScript.Application.CodeGeneration/Visitors/ExpressionInstructionProvider.cs @@ -270,7 +270,7 @@ public AddressedInstructions Visit(AssignmentExpression visitable) public AddressedInstructions Visit(MemberExpression visitable) => visitable.Empty() ? [] - : visitable.Tail?.Accept(This) ?? []; + : visitable.AccessChain.Last?.Value.Accept(This) ?? []; public AddressedInstructions Visit(DotAccess visitable) { @@ -317,7 +317,7 @@ public AddressedInstructions Visit(CallExpression visitable) if (visitable.IsEmptyCall) return []; - var methodCall = !visitable.Empty(); + var methodCall = !visitable.Member.Empty(); AddressedInstructions result = []; if (methodCall) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongAssignmentTarget.cs b/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongAssignmentTarget.cs deleted file mode 100644 index 2f781f57..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Exceptions/WrongAssignmentTarget.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; - -namespace HydraScript.Application.StaticAnalysis.Exceptions; - -[ExcludeFromCodeCoverage] -public class WrongAssignmentTarget(LeftHandSideExpression lhs) : - SemanticException( - lhs.Segment, - $"Assignment target must be variable, property or indexer. '{lhs.Id.Name}' is {lhs.GetType().Name}"); \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/IComputedTypesStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/IComputedTypesStorage.cs deleted file mode 100644 index 8c210aa1..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/IComputedTypesStorage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis; - -public interface IComputedTypesStorage -{ - public Guid Save(Type computedType); - - public Type Get(Guid computedTypeGuid); - - public void Clear(); -} \ No newline at end of file diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ComputedTypesStorage.cs b/src/Application/HydraScript.Application.StaticAnalysis/Impl/ComputedTypesStorage.cs deleted file mode 100644 index a8d81510..00000000 --- a/src/Application/HydraScript.Application.StaticAnalysis/Impl/ComputedTypesStorage.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace HydraScript.Application.StaticAnalysis.Impl; - -internal class ComputedTypesStorage : IComputedTypesStorage -{ - private readonly Dictionary _computedTypes = []; - - public Guid Save(Type computedType) - { - var guid = Guid.NewGuid(); - _computedTypes[guid] = computedType; - return guid; - } - - public Type Get(Guid computedTypeGuid) => - _computedTypes[computedTypeGuid]; - - public void Clear() => _computedTypes.Clear(); -} \ 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 ae8dd7b3..e6c0a120 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs @@ -15,7 +15,6 @@ public static IServiceCollection AddStaticAnalysis(this IServiceCollection servi 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 7bb0f789..d9f40127 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/DeclarationVisitor.cs @@ -56,8 +56,7 @@ public VisitUnit Visit(LexicalDeclaration visitable) if (_symbolTables[visitable.Scope].ContainsSymbol(new VariableSymbolId(assignment.Destination.Id))) throw new DeclarationAlreadyExists(assignment.Destination.Id); - var destinationType = assignment.DestinationType?.Accept( - _typeBuilder) ?? _typesService.Undefined; + var destinationType = assignment.DestinationType?.Accept(_typeBuilder) ?? _typesService.Undefined; if (destinationType == _typesService.Undefined && assignment.Source is ImplicitLiteral or ArrayLiteral { Expressions.Count: 0 }) @@ -80,7 +79,7 @@ public VisitUnit Visit(FunctionDeclaration visitable) visitable.ReturnStatements = returnAnalyzerResult.ReturnStatements; visitable.AllCodePathsEndedWithReturn = returnAnalyzerResult.CodePathEndedWithReturn; - var parentTable = _symbolTables[visitable.Parent.Scope]; + var parentTable = _symbolTables[visitable.Parent?.Scope ?? Scope.Empty]; var parameters = new List(); for (var i = 0; i < visitable.Arguments.Count; i++) diff --git a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs index fa217df2..df5f8689 100644 --- a/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs +++ b/src/Application/HydraScript.Application.StaticAnalysis/Visitors/SemanticChecker.cs @@ -49,7 +49,6 @@ internal class SemanticChecker : VisitorBase, private readonly IFunctionWithUndefinedReturnStorage _functionStorage; private readonly IMethodStorage _methodStorage; private readonly ISymbolTableStorage _symbolTables; - private readonly IComputedTypesStorage _computedTypes; private readonly IAmbiguousInvocationStorage _ambiguousInvocations; private readonly IVisitor _typeBuilder; @@ -58,7 +57,6 @@ public SemanticChecker( IFunctionWithUndefinedReturnStorage functionStorage, IMethodStorage methodStorage, ISymbolTableStorage symbolTables, - IComputedTypesStorage computedTypes, IAmbiguousInvocationStorage ambiguousInvocations, IVisitor typeBuilder) { @@ -66,7 +64,6 @@ public SemanticChecker( _functionStorage = functionStorage; _methodStorage = methodStorage; _symbolTables = symbolTables; - _computedTypes = computedTypes; _ambiguousInvocations = ambiguousInvocations; _typeBuilder = typeBuilder; } @@ -83,7 +80,6 @@ public Type Visit(ScriptBody visitable) _methodStorage.Clear(); _symbolTables.Clear(); - _computedTypes.Clear(); _ambiguousInvocations.Clear(); return _typesService.Undefined; @@ -308,9 +304,6 @@ public Type Visit(AssignmentExpression visitable) { var typeComparer = default(CommutativeTypeEqualityComparer); - if (visitable.Destination is CallExpression) - throw new WrongAssignmentTarget(visitable.Destination); - var sourceType = visitable.Source.Accept(This); if (!visitable.Destination.Empty()) { @@ -344,17 +337,18 @@ public Type Visit(AssignmentExpression visitable) 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) ?? _typesService.Undefined; + return visitable.Empty() + ? id.Accept(This) + : visitable.AccessChain.Last?.Value.Accept(This) ?? _typesService.Undefined; } public Type Visit(IndexAccess visitable) { - var prevTypeGuid = - visitable.Prev?.ComputedTypeGuid - ?? (visitable.Parent as MemberExpression)!.ComputedIdTypeGuid; - var prevType = _computedTypes.Get(prevTypeGuid); + var prevType = + visitable.Prev?.Accept(This) ?? + (visitable.Parent is MemberExpression { Id: IAbstractSyntaxTreeNode id } + ? id.Accept(This) + : _typesService.Undefined); if (!prevType.TryGetOperator("[]", out var indexOperator)) throw new NonAccessibleType(prevType); @@ -364,16 +358,16 @@ public Type Visit(IndexAccess visitable) if (!indexOperator.TryGetResultType(indexAccessDescriptor, out var elemType)) throw new ArrayAccessException(visitable.Segment, indexType); - visitable.ComputedTypeGuid = _computedTypes.Save(elemType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : elemType; + return elemType; } public Type Visit(DotAccess visitable) { - var prevTypeGuid = - visitable.Prev?.ComputedTypeGuid - ?? (visitable.Parent as MemberExpression)!.ComputedIdTypeGuid; - var prevType = _computedTypes.Get(prevTypeGuid); + var prevType = + visitable.Prev?.Accept(This) ?? + (visitable.Parent is MemberExpression { Id: IAbstractSyntaxTreeNode id } + ? id.Accept(This) + : _typesService.Undefined); if (prevType is not ObjectType objectType) throw new NonAccessibleType(prevType); @@ -384,8 +378,8 @@ public Type Visit(DotAccess visitable) return hasMethod ? objectType : throw new ObjectAccessException(visitable.Segment, objectType, visitable.Property); - visitable.ComputedTypeGuid = _computedTypes.Save(fieldType); - return visitable.HasNext() ? visitable.Next?.Accept(This) ?? _typesService.Undefined : fieldType; + + return fieldType; } public ObjectType Visit(WithExpression visitable) diff --git a/src/Domain/HydraScript.Domain.Constants/TokenTypes.cs b/src/Domain/HydraScript.Domain.Constants/TokenTypes.cs index 001a91c3..2afab1b6 100644 --- a/src/Domain/HydraScript.Domain.Constants/TokenTypes.cs +++ b/src/Domain/HydraScript.Domain.Constants/TokenTypes.cs @@ -71,11 +71,26 @@ public static IEnumerable Stream Tag: "Input", Pattern: "[<]{3}", Priority: 13); + + yield return new( + Tag: "Assign", + Pattern: "[+]{1,2}[=]|[-][=]|[*][=]|[/][=]|[%][=]|[|][|][=]|[&][&][=]", + Priority: 14); yield return new( Tag: "Operator", - Pattern: "[+]{1,2}|[-]|[*]|[/]|[%]|([!]|[=])[=]|([<]|[>])[=]?|[!]|[|]{2}|[&]{2}|[~]|[:]{2}|[$]", - Priority: 14); + Pattern: "[=][=]|[!][=]|[<][=]|[>][=]", + Priority: 15); + + yield return new( + Tag: "Assign", + Pattern: "[=]", + Priority: 16); + + yield return new( + Tag: "Operator", + Pattern: "[+][+]|[-]|[+*/%<>!~$]|[|][|]|[&][&]|[:][:]", + Priority: 17); yield return new( Tag: "Comma", @@ -117,11 +132,6 @@ public static IEnumerable Stream Pattern: "[]]", Priority: 109); - yield return new( - Tag: "Assign", - Pattern: "[=]", - Priority: 99); - yield return new( Tag: "QuestionMark", Pattern: "[?]", diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs index 07cde58b..90660c4e 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TextCoordinateSystemComputer.cs @@ -4,7 +4,7 @@ namespace HydraScript.Domain.FrontEnd.Lexer.Impl; public class TextCoordinateSystemComputer : ITextCoordinateSystemComputer { - private readonly SearchValues _sv = SearchValues.Create(['\n']); + private readonly SearchValues _sv = SearchValues.Create('\n'); /// public IReadOnlyList GetLines(string text) diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TokenTypesProvider.cs b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TokenTypesProvider.cs index 4e15a503..0671671f 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TokenTypesProvider.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Lexer/Impl/TokenTypesProvider.cs @@ -12,6 +12,7 @@ public FrozenDictionary GetTokenTypes() => .Select(x => x.CanIgnore ? new IgnorableType(x.Tag) : new TokenType(x.Tag)) + .Distinct() .Concat([new EndOfProgramType(), new ErrorType()]) .ToFrozenDictionary(x => x.Tag); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs index ee59c67a..c303b1de 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/IAbstractSyntaxTreeNode.cs @@ -4,7 +4,7 @@ public interface IAbstractSyntaxTreeNode : IReadOnlyList, IVisitable { - public IAbstractSyntaxTreeNode Parent { get; } + public IAbstractSyntaxTreeNode? Parent { get; } public Scope Scope { get; } public void InitScope(Scope? scope = null); public IReadOnlyList GetAllNodes(); diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs index be303205..bf6dbd2a 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/AbstractSyntaxTreeNode.cs @@ -5,9 +5,9 @@ namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast; public abstract class AbstractSyntaxTreeNode : IAbstractSyntaxTreeNode { - public IAbstractSyntaxTreeNode Parent { get; set; } = null!; + public IAbstractSyntaxTreeNode? Parent { get; internal set; } - public Scope Scope { get; protected set; } = null!; + public Scope Scope { get; protected set; } = Scope.Empty; /// Базовая стратегия - инициализация через родительский узел /// Обязательно null @@ -15,7 +15,7 @@ public virtual void InitScope(Scope? scope = null) { if (scope is not null) throw new ArgumentException("'scope' must be null"); - Scope = Parent.Scope; + Scope = Parent?.Scope ?? Scope.Empty; } public string Segment { get; init; } = string.Empty; @@ -45,13 +45,11 @@ public IReadOnlyList GetAllNodes() => public bool ChildOf(Predicate? condition = null) where T : IAbstractSyntaxTreeNode { var parent = Parent; - while (parent != default!) + while (parent != null) { if (parent is T node) { - return condition is not null - ? condition(node) - : true; + return condition?.Invoke(node) ?? true; } parent = parent.Parent; diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs index aff96ccb..259a8970 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/AfterTypesAreLoaded/FunctionDeclaration.cs @@ -48,10 +48,10 @@ public override void InitScope(Scope? scope = null) { ArgumentNullException.ThrowIfNull(scope); Scope = scope; - Scope.AddOpenScope(Parent.Scope); + Scope.AddOpenScope(Parent?.Scope ?? Scope.Empty); - _arguments.ForEach(x => x.TypeValue.Scope = Parent.Scope); - ReturnTypeValue.Scope = Parent.Scope; + _arguments.ForEach(x => x.TypeValue.Scope = Parent?.Scope ?? Scope.Empty); + ReturnTypeValue.Scope = Parent?.Scope ?? Scope.Empty; } protected override string NodeRepresentation() => diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs index 781bd7ce..d15260db 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Declarations/TypeValue.cs @@ -6,6 +6,7 @@ public abstract record TypeValue : IVisitable { public Scope Scope { get; set; } = null!; public abstract TReturn Accept(IVisitor visitor); + public abstract TypeValue DeepClone(); } [AutoVisitable] @@ -18,18 +19,24 @@ public partial record TypeIdentValue(IdentifierReference TypeId) : TypeValue public static TypeIdentValue Undefined => new(new IdentifierReference("undefined")); public override string ToString() => TypeId; + + public override TypeValue DeepClone() => new TypeIdentValue(TypeId.Clone()); } [AutoVisitable] public partial record ArrayTypeValue(TypeValue TypeValue) : TypeValue { public override string ToString() => $"{TypeValue}[]"; + + public override TypeValue DeepClone() => new ArrayTypeValue(TypeValue.DeepClone()); } [AutoVisitable] public partial record NullableTypeValue(TypeValue TypeValue) : TypeValue { public override string ToString() => $"{TypeValue}?"; + + public override TypeValue DeepClone() => new NullableTypeValue(TypeValue.DeepClone()); } public record PropertyTypeValue( @@ -38,10 +45,20 @@ public record PropertyTypeValue( { public override string ToString() => $"{Key}: {TypeValue}"; + + public PropertyTypeValue DeepClone() => this with + { + TypeValue = TypeValue.DeepClone() + }; } [AutoVisitable] public partial record ObjectTypeValue(List Properties) : TypeValue { public override string ToString() => $"{{{string.Join(';', Properties)}}}"; + + public override TypeValue DeepClone() => new ObjectTypeValue( + Properties + .Select(p => p.DeepClone()) + .ToList()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs index bb7d9289..1e101f52 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/AccessExpression.cs @@ -2,12 +2,9 @@ namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessEx public abstract class AccessExpression : Expression { - public AccessExpression? Next { get; private set; } + protected AccessExpression? Next { get; private set; } - public AccessExpression? Prev => - Parent as AccessExpression; - - public Guid ComputedTypeGuid { get; set; } = Guid.Empty; + public AccessExpression? Prev => Parent as AccessExpression; protected AccessExpression(AccessExpression? prev) { @@ -18,12 +15,10 @@ protected AccessExpression(AccessExpression? prev) } } - public bool HasNext() => - Next is not null; - - public bool HasPrev() => - Prev is not null; + public bool HasPrev() => Prev is not null; public abstract override TReturn Accept( IVisitor visitor); + + public abstract override AccessExpression Clone(); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs index 663c0555..5c142ed5 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/DotAccess.cs @@ -6,7 +6,7 @@ namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessEx public partial class DotAccess : AccessExpression { protected override IReadOnlyList Children => - HasNext() ? [Property, Next!] : [Property]; + Next is { } next ? [Property, next] : [Property]; public IdentifierReference Property { get; } @@ -17,4 +17,6 @@ public DotAccess(IdentifierReference property, AccessExpression? prev = null) : } protected override string NodeRepresentation() => "."; + + public override DotAccess Clone() => new(Property.Clone(), Prev?.Clone()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs index 633dd27c..8bef772c 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AccessExpressions/IndexAccess.cs @@ -4,7 +4,7 @@ namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessEx public partial class IndexAccess : AccessExpression { protected override IReadOnlyList Children => - HasNext() ? [Index, Next!] : [Index]; + Next is { } next ? [Index, next] : [Index]; public Expression Index { get; } @@ -15,4 +15,6 @@ public IndexAccess(Expression index, AccessExpression? prev = null) : base(prev) } protected override string NodeRepresentation() => "[]"; + + public override IndexAccess Clone() => new(Index.Clone(), Prev?.Clone()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs index 9408c52d..d1d9e2f5 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/AssignmentExpression.cs @@ -7,12 +7,12 @@ public partial class AssignmentExpression : Expression { protected override IReadOnlyList Children { get; } - public LeftHandSideExpression Destination { get; } + public MemberExpression Destination { get; } public Expression Source { get; } public TypeValue? DestinationType { get; } public AssignmentExpression( - LeftHandSideExpression lhs, + MemberExpression lhs, Expression source, TypeValue? destinationType = null) { @@ -35,4 +35,7 @@ public override void InitScope(Scope? scope = null) } protected override string NodeRepresentation() => "="; + + public override AssignmentExpression Clone() => + new (Destination.Clone(), Source.Clone(), DestinationType); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs index 541cde20..5b3b7c6d 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/BinaryExpression.cs @@ -23,4 +23,6 @@ public BinaryExpression(Expression left, string @operator, Expression right) } protected override string NodeRepresentation() => Operator; + + public override BinaryExpression Clone() => new(Left.Clone(), Operator, Right.Clone()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs index f92e3081..db1e7bc1 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CallExpression.cs @@ -30,7 +30,8 @@ public CallExpression(MemberExpression member, List expressions) public override IdentifierReference Id => Member.Id; - public override bool Empty() => Member.Empty(); - protected override string NodeRepresentation() => "()"; + + public override CallExpression Clone() => + new(Member.Clone(), _parameters.Select(x => x.Clone()).ToList()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs index 8aca5171..366daf93 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/CastAsExpression.cs @@ -30,6 +30,8 @@ public override void InitScope(Scope? scope = null) protected override string NodeRepresentation() => $"as {Cast}"; + public override CastAsExpression Clone() => new(Expression.Clone(), Cast); + public enum DestinationType { Undefined, diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs index 2aeb7f64..cbb5b132 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ArrayLiteral.cs @@ -25,4 +25,7 @@ public ArrayLiteral(List expressions) } protected override string NodeRepresentation() => "[]"; + + public override ArrayLiteral Clone() => + new(_expressions.Select(x => x.Clone()).ToList()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs index fd43ece9..c93defb4 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/ObjectLiteral.cs @@ -18,10 +18,10 @@ public override IdentifierReference Id { get { - if (Parent is AssignmentExpression assignment) + if (Parent is AssignmentExpression assignment) return assignment.Destination.Id; - if (Parent is WithExpression{Parent:AssignmentExpression withAssignment}) + if (Parent is WithExpression { Parent: AssignmentExpression withAssignment }) return withAssignment.Destination.Id; return new(NullId); @@ -40,8 +40,11 @@ public override void InitScope(Scope? scope = null) { ArgumentNullException.ThrowIfNull(scope); Scope = scope; - Scope.AddOpenScope(Parent.Scope); + Scope.AddOpenScope(Parent?.Scope ?? Scope.Empty); } protected override string NodeRepresentation() => "{}"; + + public override ObjectLiteral Clone() => + new(_properties.Select(x => x.Clone()).ToList()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs index 5b85ce3d..76c5d282 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ComplexLiterals/Property.cs @@ -31,4 +31,6 @@ public void Deconstruct(out string id, out Expression expr) } protected override string NodeRepresentation() => ":"; + + public override Property Clone() => new(Id.Clone(), Expression.Clone()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs index fc5202e1..4013176f 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/ConditionalExpression.cs @@ -23,4 +23,7 @@ public ConditionalExpression(Expression test, Expression consequent, Expression } protected override string NodeRepresentation() => "?:"; + + public override ConditionalExpression Clone() => + new(Test.Clone(), Consequent.Clone(), Alternate.Clone()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs index a3766257..c3500d9f 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/Expression.cs @@ -2,6 +2,8 @@ namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; public abstract class Expression : AbstractSyntaxTreeNode { + public abstract Expression Clone(); + public abstract override TReturn Accept( IVisitor visitor); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs index f15d9497..63dd3be1 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/LeftHandSideExpression.cs @@ -5,6 +5,4 @@ namespace HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; public abstract class LeftHandSideExpression : Expression { public abstract IdentifierReference Id { get; } - - public abstract bool Empty(); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs index b298cdf6..e47331db 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/MemberExpression.cs @@ -9,33 +9,41 @@ public partial class MemberExpression : LeftHandSideExpression private readonly IdentifierReference _identifierReference; protected override IReadOnlyList Children => - AccessChain is not null ? [Id, AccessChain] : [Id]; + AccessChain.First is { Value: { } head } ? [Id, head] : [Id]; - public AccessExpression? AccessChain { get; } - public AccessExpression? Tail { get; } + public LinkedList AccessChain { get; } - public Guid ComputedIdTypeGuid { get; set; } = Guid.Empty; - - public MemberExpression(IdentifierReference identifierReference) + public MemberExpression(IdentifierReference identifierReference) : + this(identifierReference, []) { - _identifierReference = identifierReference; - _identifierReference.Parent = this; } public MemberExpression( IdentifierReference identifierReference, - AccessExpression? accessChain, - AccessExpression? tail) : this(identifierReference) + LinkedList accessChain) { - AccessChain = accessChain; - AccessChain?.Parent = this; + _identifierReference = identifierReference; + _identifierReference.Parent = this; - Tail = tail; + AccessChain = accessChain; + AccessChain.First?.Value.Parent = this; } public override IdentifierReference Id => _identifierReference; - public override bool Empty() => AccessChain is null; + public bool Empty() => AccessChain.Count == 0; protected override string NodeRepresentation() => nameof(MemberExpression); + + public override MemberExpression Clone() + { + var clonedAccessChain = new LinkedList(); + var clonedTail = AccessChain.Last?.Value.Clone(); + while (clonedTail != null) + { + clonedAccessChain.AddFirst(clonedTail); + clonedTail = clonedTail.Prev; + } + return new MemberExpression(Id.Clone(), clonedAccessChain); + } } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs index 198214d1..22c05716 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/AbstractLiteral.cs @@ -11,6 +11,6 @@ public abstract partial class AbstractLiteral(TypeValue type) : PrimaryExpressio public override void InitScope(Scope? scope = null) { base.InitScope(scope); - Type.Scope = Parent.Scope; + Type.Scope = Parent?.Scope ?? Scope.Empty; } } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/EnvVarReference.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/EnvVarReference.cs index 91ed31a2..16a1eee3 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/EnvVarReference.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/EnvVarReference.cs @@ -7,6 +7,8 @@ public partial class EnvVarReference(string name) : IdentifierReference(name) { protected override string NodeRepresentation() => ZString.Concat('$', Name); + public override EnvVarReference Clone() => new(Name); + public override ValueDto ToValueDto() => ValueDto.EnvDto(Name); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs index 407577f6..5c2ff913 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/IdentifierReference.cs @@ -7,6 +7,8 @@ public partial class IdentifierReference(string name) : PrimaryExpression protected override string NodeRepresentation() => Name; + public override IdentifierReference Clone() => new(Name); + public override ValueDto ToValueDto() => ValueDto.NameDto(Name); diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs index 3582930f..8ff1f60a 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ImplicitLiteral.cs @@ -12,12 +12,14 @@ public partial class ImplicitLiteral(TypeValue type) : AbstractLiteral(type) TypeIdentValue { TypeId.Name: "boolean" } => false, TypeIdentValue { TypeId.Name: "null" } or NullableTypeValue => null, ArrayTypeValue => new List(), - _ => new Undefined() + _ => default(Undefined) }; protected override string NodeRepresentation() => $"Implicit {Type}"; + public override ImplicitLiteral Clone() => new(Type.DeepClone()); + public void SetValue(object? value) => _defaultValue = value; public bool IsDefined => _defaultValue is not Undefined; @@ -29,5 +31,5 @@ _defaultValue is null ? "null" : _defaultValue.ToString()!); - private sealed class Undefined; + private readonly struct Undefined; } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs index 68d169a0..4b703f59 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/Literal.cs @@ -11,7 +11,7 @@ public partial class Literal : AbstractLiteral public Literal( TypeValue type, object? value, - string segment, + string segment = "", string? label = null) : base(type) { _label = (label ?? value?.ToString())!; @@ -21,6 +21,8 @@ public Literal( protected override string NodeRepresentation() => _label; + public override Literal Clone() => new(Type.DeepClone(), _value, Segment, _label); + public override ValueDto ToValueDto() => ValueDto.ConstantDto(_value, _label); diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs index 655e09c9..e1abc377 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/PrimaryExpressions/ValueDto.cs @@ -14,7 +14,7 @@ public static ValueDto NameDto(string name) => public static ValueDto EnvDto(string name) => new(ValueDtoType.Env, name, Value: null, Label: null); -}; +} public enum ValueDtoType { diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs index 76ed1059..66dd6f9b 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/UnaryExpression.cs @@ -19,4 +19,6 @@ public UnaryExpression(string @operator, Expression expression) } protected override string NodeRepresentation() => Operator; + + public override UnaryExpression Clone() => new(Operator, Expression.Clone()); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/WithExpression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/WithExpression.cs index 5fd50c11..6d10e9b0 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/WithExpression.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Expressions/WithExpression.cs @@ -24,4 +24,7 @@ public WithExpression(Expression expression, ObjectLiteral objectLiteral) } protected override string NodeRepresentation() => "with"; + + public override WithExpression Clone() => + new(Expression.Clone(), ObjectLiteral.Clone()); } diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs index 36bf5975..562ecd94 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/Ast/Nodes/Statements/BlockStatement.cs @@ -20,7 +20,7 @@ public override void InitScope(Scope? scope = null) { ArgumentNullException.ThrowIfNull(scope); Scope = scope; - Scope.AddOpenScope(Parent.Scope); + Scope.AddOpenScope(Parent?.Scope ?? Scope.Empty); } protected override string NodeRepresentation() => "{}"; diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Declaration.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Declaration.cs new file mode 100644 index 00000000..6ea45923 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Declaration.cs @@ -0,0 +1,215 @@ +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl; + +public partial class TopDownParser +{ + /// + /// Declaration -> LexicalDeclaration + /// FunctionDeclaration + /// TypeDeclaration + /// + private Declaration Declaration() + { + if (CurrentIsKeyword("function")) + { + return FunctionDeclaration(); + } + + if (CurrentIsKeyword("let") || CurrentIsKeyword("const")) + { + return LexicalDeclaration(); + } + + if (CurrentIsKeyword("type")) + { + return TypeDeclaration(); + } + + throw new ParserException(nameof(Declaration), _tokens.Current); + } + + /// + /// FunctionDeclaration -> 'function' "Ident" '(' FunctionParameters? ')' Type? BlockStatement + /// + private FunctionDeclaration FunctionDeclaration() + { + Expect("Keyword", "function"); + var ident = Expect("Ident"); + + Expect("LeftParen"); + var args = new List(); + var indexOfFirstDefaultArgument = int.MaxValue; + while (CurrentIs("Ident")) + { + var arg = Expect("Ident"); + if (CurrentIs("Colon")) + { + Expect("Colon"); + var type = TypeValue(); + args.Add(new NamedArgument(arg.Value, type)); + } + else if (CurrentIs("Assign")) + { + Expect("Assign", "="); + var value = LiteralNode(); + indexOfFirstDefaultArgument = args.Count < indexOfFirstDefaultArgument + ? args.Count + : indexOfFirstDefaultArgument; + args.Add(new DefaultValueArgument(arg.Value, value)); + } + else throw new ParserException($"Expected ':' or '=' after argument name <{arg}>"); + + if (!CurrentIs("RightParen")) + Expect("Comma"); + } + + Expect("RightParen"); + TypeValue returnType = TypeIdentValue.Undefined; + + if (CurrentIs("Colon")) + { + Expect("Colon"); + returnType = TypeValue(); + } + + var name = new IdentifierReference(ident.Value) { Segment = ident.Segment }; + return new FunctionDeclaration(name, returnType, args, BlockStatement(), indexOfFirstDefaultArgument) + { Segment = ident.Segment }; + } + + /// + /// LexicalDeclaration -> LetOrConst "Ident" Initialization (',' "Ident" Initialization)* + /// + private LexicalDeclaration LexicalDeclaration() + { + var readOnly = CurrentIsKeyword("const"); + Expect("Keyword", readOnly ? "const" : "let"); + var declaration = new LexicalDeclaration(readOnly); + + declaration.AddAssignment(DeclarationAssignmentExpression()); + + while (CurrentIs("Comma")) + { + Expect("Comma"); + declaration.AddAssignment(DeclarationAssignmentExpression()); + } + + return declaration; + } + + /// + /// Initialization -> Typed | Initializer + /// Typed -> Type Initializer? + /// Initializer -> '=' Expression + /// + private AssignmentExpression DeclarationAssignmentExpression() + { + var ident = Expect("Ident"); + var identRef = new IdentifierReference(ident.Value) { Segment = ident.Segment }; + + if (CurrentIs("Assign")) + { + var assignSegment = Expect("Assign", "=").Segment; + return new AssignmentExpression( + new MemberExpression(identRef), Expression()) + { Segment = assignSegment }; + } + + if (CurrentIs("Colon")) + { + Expect("Colon"); + var type = TypeValue(); + var assignSegment = CurrentIs("Assign") ? Expect("Assign", "=").Segment : string.Empty; + var expression = assignSegment is not "" ? Expression() : new ImplicitLiteral(type); + return new AssignmentExpression( + new MemberExpression(identRef), expression, type) + { Segment = assignSegment }; + } + + throw new ParserException($"Expected ':' or '=' after var name <{ident}>"); + } + + /// + /// TypeDeclaration -> 'type' "Ident" = TypeValue + /// + private TypeDeclaration TypeDeclaration() + { + var typeWord = Expect("Keyword", "type"); + var ident = Expect("Ident"); + Expect("Assign", "="); + var type = TypeValue(); + + var typeId = new IdentifierReference(name: ident.Value) + { Segment = ident.Segment }; + + return new TypeDeclaration(typeId, type) { Segment = typeWord.Segment + ident.Segment }; + } + + /// + /// TypeValue -> TypeValueBase TypeValueSuffix* + /// + private TypeValue TypeValue() + { + if (CurrentIs("Ident")) + { + var ident = Expect("Ident"); + var identType = new TypeIdentValue( + TypeId: new IdentifierReference(name: ident.Value) + { Segment = ident.Segment }); + + return WithSuffix(identType); + } + + if (CurrentIs("LeftCurl")) + { + Expect("LeftCurl"); + var propertyTypes = new List(); + while (CurrentIs("Ident")) + { + var ident = Expect("Ident"); + Expect("Colon"); + var propType = TypeValue(); + propertyTypes.Add( + new PropertyTypeValue( + ident.Value, + propType)); + Expect("SemiColon"); + } + + Expect("RightCurl"); + + return WithSuffix(new ObjectTypeValue(propertyTypes)); + } + + throw new ParserException(nameof(TypeValue), _tokens.Current); + } + + /// + /// TypeValueSuffix -> '['']' | '?' + /// + private TypeValue WithSuffix(TypeValue baseType) + { + var type = baseType; + while (CurrentIs("LeftBracket") || CurrentIs("QuestionMark")) + { + if (CurrentIs("LeftBracket")) + { + Expect("LeftBracket"); + Expect("RightBracket"); + type = new ArrayTypeValue(type); + } + else if (CurrentIs("QuestionMark")) + { + Expect("QuestionMark"); + type = new NullableTypeValue(type); + } + } + + return type; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Expression.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Expression.cs new file mode 100644 index 00000000..2f470a02 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Expression.cs @@ -0,0 +1,445 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl; + +public partial class TopDownParser +{ + /// + /// Expression -> CastExpression | AssignmentExpression + /// AssignmentExpression -> MemberExpression "Assign" Expression + /// + private Expression Expression() + { + var expr = CastExpression(); + if (expr is MemberExpression lhs && CurrentIs("Assign")) + { + var assign = Expect("Assign"); + var source = assign.Value is "=" + ? Expression() + : new BinaryExpression( + lhs.Empty() ? lhs.Id.Clone() : lhs.Clone(), + assign.Value[..^1], + Expression()); + return new AssignmentExpression(lhs, source) + { Segment = assign.Segment }; + } + + return expr; + } + + /// + /// CallExpression -> MemberExpression Arguments + /// Arguments -> '(' (Expression ',')* ')' + /// + private Expression CallExpression() + { + var member = MemberExpression(); + if (CurrentIs("LeftParen")) + { + Expect("LeftParen"); + var expressions = new List(); + + while (CurrentIsExpression()) + { + expressions.Add(Expression()); + if (!CurrentIs("RightParen")) + Expect("Comma"); + } + + var rp = Expect("RightParen"); + return new CallExpression(member, expressions) + { Segment = member.Segment + rp.Segment }; + } + + return member.Empty() && !CurrentIs("Assign") ? member.Id : member; + } + + /// + /// MemberExpression -> Var ('[' Expression ']' | '.' 'Ident')* + /// + private MemberExpression MemberExpression() + { + var memberRoot = Var(); + var accessChain = new LinkedList(); + while (CurrentIs("LeftBracket") || CurrentIs("Dot")) + { + if (CurrentIs("LeftBracket")) + { + var lb = Expect("LeftBracket").Segment; + var expr = Expression(); + var rb = Expect("RightBracket").Segment; + accessChain.AddLast( + new IndexAccess(expr, accessChain.Last?.Value) + { Segment = lb + rb }); + } + else if (CurrentIs("Dot")) + { + var access = Expect("Dot"); + var propToken = Expect("Ident"); + var propIdent = new IdentifierReference(propToken.Value) + { Segment = propToken.Segment }; + accessChain.AddLast( + new DotAccess(propIdent, accessChain.Last?.Value) + { Segment = access.Segment }); + } + } + + return new MemberExpression(memberRoot, accessChain) + { + Segment = memberRoot.Segment + }; + } + + /// + /// CastExpression -> WithExpression 'as' TypeValue + /// + private Expression CastExpression() + { + var withExpr = WithExpression(); + if (CurrentIsKeyword("as")) + { + var asKeyword = Expect("Keyword", "as"); + var type = TypeValue(); + return new CastAsExpression(withExpr, type) { Segment = asKeyword.Segment }; + } + + return withExpr; + } + + /// + /// WithExpression -> ConditionalExpression 'with' ObjectLiteral + /// + private Expression WithExpression() + { + var cond = ConditionalExpression(); + if (CurrentIsKeyword("with")) + { + var withKeyword = Expect("Keyword", "with"); + var objectLiteral = ObjectLiteral(); + return new WithExpression(cond, objectLiteral) { Segment = withKeyword.Segment }; + } + + return cond; + } + + /// + /// ConditionalExpression -> OrExpression ('?' Expression ':' Expression)? + /// + private Expression ConditionalExpression() + { + var test = OrExpression(); + if (CurrentIs("QuestionMark")) + { + Expect("QuestionMark"); + var consequent = Expression(); + Expect("Colon"); + var alternate = Expression(); + return new ConditionalExpression(test, consequent, alternate) + { + Segment = consequent.Segment + alternate.Segment + }; + } + + return test; + } + + /// + /// OrExpression -> AndExpression ('||' AndExpression)* + /// + private Expression OrExpression() + { + var left = AndExpression(); + while (CurrentIsOperator("||")) + { + var op = Expect("Operator"); + var right = AndExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + /// + /// AndExpression -> EqExpression ('&&' EqExpression)* + /// + private Expression AndExpression() + { + var left = EqualityExpression(); + while (CurrentIsOperator("&&")) + { + var op = Expect("Operator"); + var right = EqualityExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + /// + /// EqExpression -> RelExpression (('=='|'!=') RelExpression)* + /// + private Expression EqualityExpression() + { + var left = RelationExpression(); + while (CurrentIsOperator("==") || CurrentIsOperator("!=")) + { + var op = Expect("Operator"); + var right = RelationExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + /// + /// RelExpression -> AddExpression (('<'|'>'|'≤'|'≥') AddExpression)* + /// + private Expression RelationExpression() + { + var left = AdditiveExpression(); + while (CurrentIsOperator(">") || CurrentIsOperator("<") || + CurrentIsOperator(">=") || CurrentIsOperator("<=")) + { + var op = Expect("Operator"); + var right = AdditiveExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + /// + /// AddExpression -> MulExpression (('+'|'-') MulExpression)* + /// + private Expression AdditiveExpression() + { + var left = MultiplicativeExpression(); + while (CurrentIsOperator("+") || CurrentIsOperator("-")) + { + var op = Expect("Operator"); + var right = MultiplicativeExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + /// + /// MulExpression -> UnaryExpression (('*'|'/'|'%'|'++'|'::') UnaryExpression)* + /// + private Expression MultiplicativeExpression() + { + var left = UnaryExpression(); + while (CurrentIsOperator("*") || CurrentIsOperator("/") || CurrentIsOperator("%") + || CurrentIsOperator("++") || CurrentIsOperator("::")) + { + var op = Expect("Operator"); + var right = UnaryExpression(); + left = new BinaryExpression(left, op.Value, right) + { + Segment = op.Segment + }; + } + + return left; + } + + /// + /// UnaryExpression -> LeftHandSideExpression | ('-'|'!'|'~') UnaryExpression + /// + private Expression UnaryExpression() + { + if (CurrentIsUnaryOperator(expectEnv: false)) + { + var op = Expect("Operator"); + return new UnaryExpression(op.Value, UnaryExpression()) + { + Segment = op.Segment + }; + } + + return LeftHandSideExpression(); + } + + /// + /// LeftHandSideExpression -> PrimaryExpression + /// ParenthesizedExpression + /// ComplexLiteral + /// MemberExpression + /// CallExpression + /// ParenthesizedExpression -> '(' Expression ')' + /// + private Expression LeftHandSideExpression() + { + if (CurrentIs("LeftParen")) + { + Expect("LeftParen"); + var expr = Expression(); + Expect("RightParen"); + return expr; + } + + if (CurrentIs("LeftCurl") || CurrentIs("LeftBracket")) + { + return ComplexLiteral(); + } + + if (CurrentIs("Ident") || CurrentIsOperator("$")) + { + return CallExpression(); + } + + return PrimaryExpression(); + } + + /// + /// PrimaryExpression -> Var | Literal + /// + private PrimaryExpression PrimaryExpression() + { + return LiteralNode(); + } + + /// + /// Var -> "Ident" | EnvVar + /// EnvVar -> '$' "Ident" + /// + private IdentifierReference Var() + { + if (CurrentIs("Ident")) + { + var ident = Expect("Ident"); + return new IdentifierReference(ident.Value) + { + Segment = ident.Segment + }; + } + + var dollar = Expect("Operator"); + var envIdent = Expect("Ident"); + return new EnvVarReference(envIdent.Value) + { + Segment = dollar.Segment + envIdent.Segment + }; + } + + /// + /// Literal -> "NullLiteral" + /// "IntegerLiteral" + /// "FloatLiteral" + /// "StringLiteral" + /// "BooleanLiteral" + /// + private Literal LiteralNode() + { + var segment = _tokens.Current.Segment; + if (CurrentIs("StringLiteral")) + { + var str = Expect("StringLiteral"); + return Literal.String( + value: Regex.Unescape(str.Value.Trim('"')), + segment, + label: str.Value + .Replace(@"\", @"\\") + .Replace(@"""", @"\""")); + } + + if (CurrentIs("NullLiteral")) + { + Expect("NullLiteral"); + return Literal.Null(segment); + } + + return _tokens.Current.Type.Tag switch + { + "IntegerLiteral" => Literal.Number(value: double.Parse(Expect("IntegerLiteral").Value), segment), + "FloatLiteral" => Literal.Number( + value: double.Parse( + Expect("FloatLiteral").Value, + CultureInfo.InvariantCulture), + segment), + "BooleanLiteral" => Literal.Boolean(value: bool.Parse(Expect("BooleanLiteral").Value), segment), + _ => throw new ParserException("Literal", _tokens.Current) + }; + } + + /// + /// ComplexLiteral -> ObjectLiteral | ArrayLiteral + /// + private ComplexLiteral ComplexLiteral() + { + if (CurrentIs("LeftCurl")) + { + return ObjectLiteral(); + } + + return ArrayLiteral(); + } + + /// + /// ObjectLiteral -> '{' PropertyDefinitionList '}' + /// PropertyDefinitionList -> (FieldProperty ';')* + /// FieldProperty -> "Ident" ':' Expression + /// + private ObjectLiteral ObjectLiteral() + { + Expect("LeftCurl"); + var properties = new List(); + while (CurrentIs("Ident")) + { + var idToken = Expect("Ident"); + var id = new IdentifierReference(idToken.Value) + { Segment = idToken.Segment }; + + Expect("Colon"); + var expr = Expression(); + properties.Add(new Property(id, expr) { Segment = idToken.Segment }); + + Expect("SemiColon"); + } + + Expect("RightCurl"); + return new ObjectLiteral(properties); + } + + /// + /// ArrayLiteral -> '[' ElementList ']' + /// ElementList -> (Expression ',')* + /// + private ArrayLiteral ArrayLiteral() + { + var lb = Expect("LeftBracket").Segment; + var expressions = new List(); + while (CurrentIsExpression()) + { + expressions.Add(Expression()); + if (!CurrentIs("RightBracket")) + { + Expect("Comma"); + } + } + + var rb = Expect("RightBracket").Segment; + return new ArrayLiteral(expressions) { Segment = lb + rb }; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Statement.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Statement.cs new file mode 100644 index 00000000..d68ca9a9 --- /dev/null +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.Statement.cs @@ -0,0 +1,162 @@ +using HydraScript.Domain.Constants; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; + +namespace HydraScript.Domain.FrontEnd.Parser.Impl; + +public partial class TopDownParser +{ + /// + /// Statement -> BlockStatement + /// ExpressionStatement + /// IfStatement + /// WhileStatement + /// ContinueStatement + /// BreakStatement + /// ReturnStatement + /// OutputStatement + /// InputStatement + /// + private Statement Statement() + { + if (CurrentIs("Ident") || CurrentIsLiteral() || + CurrentIs("LeftParen") || CurrentIsUnaryOperator()) + return ExpressionStatement(); + + if (CurrentIs("LeftCurl")) + return BlockStatement(); + + if (CurrentIsKeyword("return")) + return ReturnStatement(); + + if (CurrentIsKeyword("break")) + return new InsideStatementJump(InsideStatementJumpKeyword.Break) + { + Segment = Expect("Keyword", "break").Segment + }; + + if (CurrentIsKeyword("continue")) + return new InsideStatementJump(InsideStatementJumpKeyword.Continue) + { + Segment = Expect("Keyword", "continue").Segment + }; + + if (CurrentIsKeyword("if")) + return IfStatement(); + + if (CurrentIsKeyword("while")) + return WhileStatement(); + + if (CurrentIs("Output")) + return OutputStatement(); + + if (CurrentIs("Input")) + return InputStatement(); + + throw new ParserException(nameof(Statement), _tokens.Current); + } + + /// + /// BlockStatement -> '{' StatementList '}' + /// + private BlockStatement BlockStatement() + { + Expect("LeftCurl"); + var block = new BlockStatement(StatementList()); + Expect("RightCurl"); + + return block; + } + + /// + /// ExpressionStatement -> Expression + /// + private ExpressionStatement ExpressionStatement() + { + return new(Expression()); + } + + /// + /// ReturnStatement -> 'return' Expression? + /// + private ReturnStatement ReturnStatement() + { + var ret = Expect("Keyword", "return"); + if (CurrentIsExpression()) + { + return new ReturnStatement(Expression()) { Segment = ret.Segment }; + } + + return new ReturnStatement { Segment = ret.Segment }; + } + + /// + /// IfStatement -> 'if' '(' Expression ')' Statement ('else' Statement)? + /// + private IfStatement IfStatement() + { + var token = Expect("Keyword", "if"); + Expect("LeftParen"); + var expr = Expression(); + Expect("RightParen"); + var then = Statement(); + if (CurrentIsKeyword("else")) + { + Expect("Keyword", "else"); + var @else = Statement(); + return new IfStatement(expr, then, @else) { Segment = token.Segment }; + } + + return new IfStatement(expr, then) { Segment = token.Segment }; + } + + /// + /// WhileStatement -> 'while' '(' Expression ')' Statement + /// + private WhileStatement WhileStatement() + { + var token = Expect("Keyword", "while"); + Expect("LeftParen"); + var expr = Expression(); + Expect("RightParen"); + var stmt = Statement(); + return new WhileStatement(expr, stmt) { Segment = token.Segment }; + } + + /// + /// OutputStatement -> '>>>' Expression + /// + private OutputStatement OutputStatement() + { + Expect("Output"); + return new OutputStatement(Expression()); + } + + /// + /// InputStatement -> '<<<' (Ident | EnvVar) + /// + private InputStatement InputStatement() + { + var input = Expect("Input"); + if (CurrentIsOperator("$")) + { + var dollar = Expect("Operator", "$"); + var envIdent = Expect("Ident"); + return new InputStatement( + new EnvVarReference(envIdent.Value) + { + Segment = dollar.Segment + envIdent.Segment + }) + { + Segment = input.Segment + envIdent.Segment + }; + } + + var ident = Expect("Ident"); + return new InputStatement(new IdentifierReference(ident.Value) { Segment = ident.Segment }) + { + Segment = input.Segment + ident.Segment + }; + } +} \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs index f1c00381..8cd35303 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Impl/TopDownParser.cs @@ -1,20 +1,11 @@ -using System.Globalization; -using System.Text.RegularExpressions; using HydraScript.Domain.Constants; using HydraScript.Domain.FrontEnd.Lexer; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.ComplexLiterals; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; -using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; namespace HydraScript.Domain.FrontEnd.Parser.Impl; -public class TopDownParser(ILexer lexer) : IParser +public partial class TopDownParser(ILexer lexer) : IParser { private IEnumerator _tokens = Enumerable.Empty().GetEnumerator(); @@ -33,9 +24,9 @@ private Token Expect(string expectedTag, string? expectedValue = null) var current = _tokens.Current; if (!CurrentIs(expectedTag)) - throw new ParserException(_tokens.Current.Segment, expectedTag, _tokens.Current); + throw new ParserException(expectedTag, _tokens.Current); if (_tokens.Current.Value != (expectedValue ?? _tokens.Current.Value)) - throw new ParserException(_tokens.Current.Segment, expectedValue, _tokens.Current); + throw new ParserException(expectedValue, _tokens.Current); _tokens.MoveNext(); return current; @@ -71,11 +62,16 @@ private bool CurrentIsExpression() => CurrentIs("Ident") || CurrentIsLiteral() || CurrentIsUnaryOperator() || CurrentIs("LeftParen") || CurrentIs("LeftCurl") || CurrentIs("LeftBracket"); + private bool CurrentIsStatement() => + CurrentIsExpression() || + CurrentIs("Output") || CurrentIs("Input") || + CurrentIsKeyword("return") || CurrentIsKeyword("break") || CurrentIsKeyword("continue") || + CurrentIsKeyword("if") || CurrentIsKeyword("while"); + /// /// Script -> StatementList /// - private ScriptBody Script() => - new(StatementList()); + private ScriptBody Script() => new(StatementList()); /// /// StatementList -> StatementListItem* @@ -83,10 +79,7 @@ private ScriptBody Script() => private List StatementList() { var statementList = new List(); - while (CurrentIsDeclaration() || CurrentIsExpression() || - CurrentIs("Output") || CurrentIs("Input") || - CurrentIsKeyword("return") || CurrentIsKeyword("break") || CurrentIsKeyword("continue") || - CurrentIsKeyword("if") || CurrentIsKeyword("while")) + while (CurrentIsDeclaration() || CurrentIsStatement()) { statementList.Add(StatementListItem()); } @@ -100,800 +93,8 @@ private List StatementList() private StatementListItem StatementListItem() { if (CurrentIsDeclaration()) - { return Declaration(); - } return Statement(); } - - /// - /// Statement -> BlockStatement - /// ExpressionStatement - /// IfStatement - /// WhileStatement - /// ContinueStatement - /// BreakStatement - /// ReturnStatement - /// OutputStatement - /// InputStatement - /// - private Statement Statement() - { - if (CurrentIs("Ident") || CurrentIsLiteral() || - CurrentIs("LeftParen") || CurrentIsUnaryOperator()) - return ExpressionStatement(); - - if (CurrentIs("LeftCurl")) - return BlockStatement(); - - if (CurrentIsKeyword("return")) - return ReturnStatement(); - - if (CurrentIsKeyword("break")) - return new InsideStatementJump(InsideStatementJumpKeyword.Break) - { - Segment = Expect("Keyword", "break").Segment - }; - - if (CurrentIsKeyword("continue")) - return new InsideStatementJump(InsideStatementJumpKeyword.Continue) - { - Segment = Expect("Keyword", "continue").Segment - }; - - if (CurrentIsKeyword("if")) - return IfStatement(); - - if (CurrentIsKeyword("while")) - return WhileStatement(); - - if (CurrentIs("Output")) - return OutputStatement(); - - if (CurrentIs("Input")) - return InputStatement(); - - return null!; - } - - /// - /// BlockStatement -> '{' StatementList '}' - /// - private BlockStatement BlockStatement() - { - Expect("LeftCurl"); - var block = new BlockStatement(StatementList()); - Expect("RightCurl"); - - return block; - } - - /// - /// ExpressionStatement -> Expression - /// - private ExpressionStatement ExpressionStatement() - { - return new(Expression()); - } - - /// - /// ReturnStatement -> 'return' Expression? - /// - private ReturnStatement ReturnStatement() - { - var ret = Expect("Keyword", "return"); - if (CurrentIsExpression()) - { - return new ReturnStatement(Expression()) { Segment = ret.Segment }; - } - - return new ReturnStatement { Segment = ret.Segment }; - } - - /// - /// IfStatement -> 'if' '(' Expression ')' Statement ('else' Statement)? - /// - private IfStatement IfStatement() - { - var token = Expect("Keyword", "if"); - Expect("LeftParen"); - var expr = Expression(); - Expect("RightParen"); - var then = Statement(); - if (CurrentIsKeyword("else")) - { - Expect("Keyword", "else"); - var @else = Statement(); - return new IfStatement(expr, then, @else) {Segment = token.Segment}; - } - - return new IfStatement(expr, then) {Segment = token.Segment}; - } - - /// - /// WhileStatement -> 'while' '(' Expression ')' Statement - /// - private WhileStatement WhileStatement() - { - var token = Expect("Keyword", "while"); - Expect("LeftParen"); - var expr = Expression(); - Expect("RightParen"); - var stmt = Statement(); - return new WhileStatement(expr, stmt) {Segment = token.Segment}; - } - - /// - /// OutputStatement -> '>>>' Expression - /// - private OutputStatement OutputStatement() - { - Expect("Output"); - return new OutputStatement(Expression()); - } - - /// - /// InputStatement -> '<<<' (Ident | EnvVar) - /// - private InputStatement InputStatement() - { - var input = Expect("Input"); - if (CurrentIsOperator("$")) - { - var dollar = Expect("Operator"); - var envIdent = Expect("Ident"); - return new InputStatement(new EnvVarReference(envIdent.Value) - { - Segment = dollar.Segment + envIdent.Segment - }) - { - Segment = input.Segment + envIdent.Segment - }; - } - - var ident = Expect("Ident"); - return new InputStatement(new IdentifierReference(ident.Value) { Segment = ident.Segment }) - { - Segment = input.Segment + ident.Segment - }; - } - - /// - /// TypeDeclaration -> 'type' "Ident" = TypeValue - /// - private TypeDeclaration TypeDeclaration() - { - var typeWord = Expect("Keyword", "type"); - var ident = Expect("Ident"); - Expect("Assign"); - var type = TypeValue(); - - var typeId = new IdentifierReference(name: ident.Value) - { Segment = ident.Segment }; - - return new TypeDeclaration(typeId, type) { Segment = typeWord.Segment + ident.Segment }; - } - - /// - /// TypeValue -> TypeValueBase TypeValueSuffix* - /// - private TypeValue TypeValue() - { - if (CurrentIs("Ident")) - { - var ident = Expect("Ident"); - var identType = new TypeIdentValue( - TypeId: new IdentifierReference(name: ident.Value) - { Segment = ident.Segment }); - - return WithSuffix(identType); - } - - if (CurrentIs("LeftCurl")) - { - Expect("LeftCurl"); - var propertyTypes = new List(); - while (CurrentIs("Ident")) - { - var ident = Expect("Ident"); - Expect("Colon"); - var propType = TypeValue(); - propertyTypes.Add( - new PropertyTypeValue( - ident.Value, - propType)); - Expect("SemiColon"); - } - - Expect("RightCurl"); - - return WithSuffix(new ObjectTypeValue(propertyTypes)); - } - - return null!; - } - - /// - /// TypeValueSuffix -> '['']' | '?' - /// - private TypeValue WithSuffix(TypeValue baseType) - { - var type = baseType; - while (CurrentIs("LeftBracket") || CurrentIs("QuestionMark")) - { - if (CurrentIs("LeftBracket")) - { - Expect("LeftBracket"); - Expect("RightBracket"); - type = new ArrayTypeValue(type); - } - else if (CurrentIs("QuestionMark")) - { - Expect("QuestionMark"); - type = new NullableTypeValue(type); - } - } - - return type; - } - - /// - /// Declaration -> LexicalDeclaration | FunctionDeclaration | TypeDeclaration - /// - private Declaration Declaration() - { - if (CurrentIsKeyword("function")) - { - return FunctionDeclaration(); - } - - if (CurrentIsKeyword("let") || CurrentIsKeyword("const")) - { - return LexicalDeclaration(); - } - - if (CurrentIsKeyword("type")) - { - return TypeDeclaration(); - } - - return null!; - } - - /// - /// FunctionDeclaration -> 'function' "Ident" '(' FunctionParameters? ')' Type? BlockStatement - /// - private FunctionDeclaration FunctionDeclaration() - { - Expect("Keyword", "function"); - var ident = Expect("Ident"); - - Expect("LeftParen"); - var args = new List(); - var indexOfFirstDefaultArgument = int.MaxValue; - while (CurrentIs("Ident")) - { - var arg = Expect("Ident").Value; - if (CurrentIs("Colon")) - { - Expect("Colon"); - var type = TypeValue(); - args.Add(new NamedArgument(arg, type)); - } - else if (CurrentIs("Assign")) - { - Expect("Assign"); - var value = LiteralNode(); - indexOfFirstDefaultArgument = args.Count < indexOfFirstDefaultArgument - ? args.Count - : indexOfFirstDefaultArgument; - args.Add(new DefaultValueArgument(arg, value)); - } - - if (!CurrentIs("RightParen")) - Expect("Comma"); - } - - var rp = Expect("RightParen"); - - TypeValue returnType = new TypeIdentValue( - TypeId: new IdentifierReference(name: "undefined") - { Segment = rp.Segment }); - - if (CurrentIs("Colon")) - { - Expect("Colon"); - returnType = TypeValue(); - } - - var name = new IdentifierReference(ident.Value) { Segment = ident.Segment }; - return new FunctionDeclaration(name, returnType, args, BlockStatement(), indexOfFirstDefaultArgument) - { Segment = ident.Segment }; - } - - /// - /// LexicalDeclaration -> LetOrConst "Ident" Initialization (',' "Ident" Initialization)* - /// - private LexicalDeclaration LexicalDeclaration() - { - var readOnly = CurrentIsKeyword("const"); - Expect("Keyword", readOnly ? "const" : "let"); - var declaration = new LexicalDeclaration(readOnly); - - AddToDeclaration(declaration); - - while (CurrentIs("Comma")) - { - Expect("Comma"); - AddToDeclaration(declaration); - } - - return declaration; - } - - /// - /// Initialization -> Typed | Initializer - /// Typed -> Type Initializer? - /// Initializer -> '=' Expression - /// - private void AddToDeclaration(LexicalDeclaration declaration) - { - var ident = Expect("Ident"); - var identRef = new IdentifierReference(ident.Value) { Segment = ident.Segment }; - var assignment = new AssignmentExpression( - new MemberExpression(identRef), - new ImplicitLiteral(TypeIdentValue.Undefined)) - { Segment = ident.Segment }; - - if (CurrentIs("Assign")) - { - var assignSegment = Expect("Assign").Segment; - var expression = Expression(); - assignment = new AssignmentExpression( - new MemberExpression(identRef), expression - ) { Segment = assignSegment }; - } - else if (CurrentIs("Colon")) - { - Expect("Colon"); - var type = TypeValue(); - if (CurrentIs("Assign")) - { - var assignSegment = Expect("Assign").Segment; - var expression = Expression(); - assignment = new AssignmentExpression( - new MemberExpression(identRef), - expression, type - ) { Segment = assignSegment }; - } - else - { - var expression = new ImplicitLiteral(type); - assignment = new AssignmentExpression( - lhs: new MemberExpression(identRef), - expression, - type); - } - } - declaration.AddAssignment(assignment); - } - - /// - /// Expression -> CastExpression | AssignmentExpression - /// - private Expression Expression() - { - var expr = CastExpression(); - if (expr is LeftHandSideExpression lhs && CurrentIs("Assign")) - { - var assign = Expect("Assign"); - return new AssignmentExpression(lhs, Expression()) - {Segment = assign.Segment}; - } - return expr; - } - - /// - /// CallExpression -> MemberExpression Arguments (Arguments | '[' Expression ']' | '.' 'Ident')* - /// - private Expression CallExpression() - { - var member = MemberExpression(); - if (CurrentIs("LeftParen")) - { - Expect("LeftParen"); - var expressions = new List(); - if (CurrentIsExpression()) - { - expressions.Add(Expression()); - } - - while (CurrentIs("Comma")) - { - Expect("Comma"); - expressions.Add(Expression()); - } - - var rp = Expect("RightParen"); - return new CallExpression((member as MemberExpression)!, expressions) - { Segment = member.Segment + rp.Segment }; - } - - return member; - } - - /// - /// MemberExpression -> "Ident" ('[' Expression ']' | '.' 'Ident')* - /// - private Expression MemberExpression() - { - var primary = PrimaryExpression(); - - if (!CurrentIs("LeftBracket") && !CurrentIs("Dot") && - !CurrentIs("Assign") && !CurrentIs("LeftParen")) - return primary; - - var identRef = (primary as IdentifierReference)!; - var accessChain = new List(); - while (CurrentIs("LeftBracket") || CurrentIs("Dot")) - { - Token access; - if (CurrentIs("LeftBracket")) - { - access = Expect("LeftBracket"); - var lb = access.Segment; - var expr = Expression(); - var rb = Expect("RightBracket").Segment; - accessChain.Add( - new IndexAccess(expr, accessChain.LastOrDefault()) {Segment = lb + rb} - ); - } - else if (CurrentIs("Dot")) - { - access = Expect("Dot"); - var identToken = Expect("Ident"); - var idRef = new IdentifierReference(identToken.Value) - { Segment = identToken.Segment }; - accessChain.Add( - new DotAccess(idRef, accessChain.LastOrDefault()) {Segment = access.Segment} - ); - } - } - - return new MemberExpression( - identRef, - accessChain.FirstOrDefault(), - tail: accessChain.LastOrDefault()) - { - Segment = identRef.Segment - }; - } - - /// - /// CastExpression -> WithExpression 'as' 'string' - /// - private Expression CastExpression() - { - var withExpr = WithExpression(); - if (CurrentIsKeyword("as")) - { - var asKeyword = Expect("Keyword", "as"); - var type = TypeValue(); - return new CastAsExpression(withExpr, type) {Segment = asKeyword.Segment}; - } - - return withExpr; - } - - /// - /// WithExpression -> ConditionalExpression 'with' ObjectLiteral - /// - private Expression WithExpression() - { - var cond = ConditionalExpression(); - if (CurrentIsKeyword("with")) - { - var withKeyword = Expect("Keyword", "with"); - var objectLiteral = ObjectLiteral(); - return new WithExpression(cond, objectLiteral) {Segment = withKeyword.Segment}; - } - - return cond; - } - - /// - /// ConditionalExpression -> OrExpression ('?' Expression ':' Expression)? - /// - private Expression ConditionalExpression() - { - var test = OrExpression(); - if (CurrentIs("QuestionMark")) - { - Expect("QuestionMark"); - var consequent = Expression(); - Expect("Colon"); - var alternate = Expression(); - return new ConditionalExpression(test, consequent, alternate) - { - Segment = consequent.Segment + alternate.Segment - }; - } - - return test; - } - - /// - /// OrExpression -> AndExpression ('||' AndExpression)* - /// - private Expression OrExpression() - { - var left = AndExpression(); - while (CurrentIsOperator("||")) - { - var op = Expect("Operator"); - var right = AndExpression(); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - /// - /// AndExpression -> EqExpression ('&&' EqExpression)* - /// - private Expression AndExpression() - { - var left = EqualityExpression(); - while (CurrentIsOperator("&&")) - { - var op = Expect("Operator"); - var right = EqualityExpression(); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - /// - /// EqExpression -> RelExpression (('=='|'!=') RelExpression)* - /// - private Expression EqualityExpression() - { - var left = RelationExpression(); - while (CurrentIsOperator("==") || CurrentIsOperator("!=")) - { - var op = Expect("Operator"); - var right = RelationExpression(); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - /// - /// RelExpression -> AddExpression (('<'|'>'|'≤'|'≥') AddExpression)* - /// - private Expression RelationExpression() - { - var left = AdditiveExpression(); - while (CurrentIsOperator(">") || CurrentIsOperator("<") || CurrentIsOperator(">=") || - CurrentIsOperator("<=")) - { - var op = Expect("Operator"); - var right = AdditiveExpression(); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - /// - /// AddExpression -> MulExpression (('+'|'-') MulExpression)* - /// - private Expression AdditiveExpression() - { - var left = MultiplicativeExpression(); - while (CurrentIsOperator("+") || CurrentIsOperator("-")) - { - var op = Expect("Operator"); - var right = MultiplicativeExpression(); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - /// - /// MulExpression -> UnaryExpression (('*'|'/'|'%'|'++'|'::') UnaryExpression)* - /// - private Expression MultiplicativeExpression() - { - var left = UnaryExpression(); - while (CurrentIsOperator("*") || CurrentIsOperator("/") || CurrentIsOperator("%") - || CurrentIsOperator("++") || CurrentIsOperator("::")) - { - var op = Expect("Operator"); - var right = UnaryExpression(); - left = new BinaryExpression(left, op.Value, right) - { - Segment = op.Segment - }; - } - - return left; - } - - /// - /// UnaryExpression -> LeftHandSideExpression | ('-'|'!'|'~') UnaryExpression - /// - private Expression UnaryExpression() - { - if (CurrentIsUnaryOperator(expectEnv: false)) - { - var op = Expect("Operator"); - return new UnaryExpression(op.Value, UnaryExpression()) - { - Segment = op.Segment - }; - } - - return LeftHandSideExpression(); - } - - /// - /// LeftHandSideExpression -> MemberExpression | CallExpression - /// - private Expression LeftHandSideExpression() - { - return CallExpression(); - } - - /// - /// PrimaryExpression -> "Ident" | EnvVar | Literal | '(' Expression ')' | ObjectLiteral | ArrayLiteral - /// EnvVar -> '$' "Ident" - /// - private Expression PrimaryExpression() - { - if (CurrentIs("LeftParen")) - { - Expect("LeftParen"); - var expr = Expression(); - Expect("RightParen"); - return expr; - } - - if (CurrentIs("Ident")) - { - var ident = Expect("Ident"); - return new IdentifierReference(ident.Value) - { - Segment = ident.Segment - }; - } - - if (CurrentIsOperator("$")) - { - var dollar = Expect("Operator"); - var ident = Expect("Ident"); - return new EnvVarReference(ident.Value) - { - Segment = dollar.Segment + ident.Segment - }; - } - - if (CurrentIsLiteral()) - { - return LiteralNode(); - } - - if (CurrentIs("LeftCurl")) - { - return ObjectLiteral(); - } - - if (CurrentIs("LeftBracket")) - { - return ArrayLiteral(); - } - - return null!; - } - - /// - /// Literal -> "NullLiteral" - /// "IntegerLiteral" - /// "FloatLiteral" - /// "StringLiteral" - /// "BooleanLiteral" - /// - private Literal LiteralNode() - { - var segment = _tokens.Current.Segment; - if (CurrentIs("StringLiteral")) - { - var str = Expect("StringLiteral"); - return Literal.String( - value: Regex.Unescape(str.Value.Trim('"')), - segment, - label: str.Value - .Replace(@"\", @"\\") - .Replace(@"""", @"\""")); - } - - if (CurrentIs("NullLiteral")) - { - Expect("NullLiteral"); - return Literal.Null(segment); - } - - return _tokens.Current.Type.Tag switch - { - "IntegerLiteral" => Literal.Number(value: double.Parse(Expect("IntegerLiteral").Value), segment), - "FloatLiteral" => Literal.Number( - value: double.Parse( - Expect("FloatLiteral").Value, - CultureInfo.InvariantCulture), - segment), - "BooleanLiteral" => Literal.Boolean(value: bool.Parse(Expect("BooleanLiteral").Value), segment), - _ => throw new ParserException("There are no more supported literals") - }; - } - - /// - /// ObjectLiteral -> '{' PropertyDefinitionList '}' - /// - private ObjectLiteral ObjectLiteral() - { - Expect("LeftCurl"); - var properties = new List(); - while (CurrentIs("Ident")) - { - var idToken = Expect("Ident"); - var id = new IdentifierReference(idToken.Value) - { Segment = idToken.Segment }; - - Expect("Colon"); - var expr = Expression(); - properties.Add(new Property(id, expr) { Segment = idToken.Segment }); - - Expect("SemiColon"); - } - Expect("RightCurl"); - return new ObjectLiteral(properties); - } - - /// - /// ArrayLiteral -> '[' ElementList ']' - /// - private ArrayLiteral ArrayLiteral() - { - var lb = Expect("LeftBracket").Segment; - var expressions = new List(); - while (CurrentIsExpression()) - { - expressions.Add(Expression()); - if (!CurrentIs("RightBracket")) - { - Expect("Comma"); - } - } - var rb = Expect("RightBracket").Segment; - return new ArrayLiteral(expressions) {Segment = lb + rb}; - } } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs index 995dbf2d..ac473f48 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/ParserException.cs @@ -12,8 +12,8 @@ public ParserException(string message) : base(message) { } protected ParserException(string message, Exception inner) : base(message, inner) { } - public ParserException(Segment segment, string? expected, Token actual) : - base($"Wrong syntax: {segment} expected {expected}; actual = ({actual.Type.Tag}, {actual.Value})") + public ParserException(string? expected, Token actual) : + base($"Wrong syntax: {actual.Segment} expected {expected}; actual = ({actual.Type.Tag}, {actual.Value})") { } } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs index 86e00d17..c0c80dce 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/Scope.cs @@ -1,12 +1,24 @@ namespace HydraScript.Domain.FrontEnd.Parser; -public record Scope +public sealed record Scope { - public Guid Id { get; } = Guid.NewGuid(); + private Scope(Guid id) + { + Id = id; + } + + public Scope() : this(Guid.NewGuid()) + { + } + + public Guid Id { get; } + public Scope? OpenScope { get; private set; } public void AddOpenScope(Scope scope) => OpenScope = scope; public override string ToString() => Id.ToString(); + + public static readonly Scope Empty = new(Guid.Empty); } \ No newline at end of file diff --git a/src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt b/src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt index 91978cbf..cc36cb71 100644 --- a/src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt +++ b/src/Domain/HydraScript.Domain.FrontEnd/Parser/grammar.txt @@ -1,7 +1,6 @@ Script -> StatementList StatementList -> StatementListItem* -StatementListItem -> Statement - Declaration +StatementListItem -> Statement | Declaration Statement -> BlockStatement ExpressionStatement @@ -20,18 +19,29 @@ Declaration -> LexicalDeclaration BlockStatement -> '{' StatementList '}' ExpressionStatement -> Expression -Expression -> CastExpression - AssignmentExpression +Expression -> CastExpression | AssignmentExpression +CastExpression -> WithExpression 'as' TypeValue +AssignmentExpression -> MemberExpression "Assign" Expression -CastExpression -> WithExpression 'as' 'string' +LeftHandSideExpression -> PrimaryExpression + ParenthesizedExpression + ComplexLiteral + MemberExpression + CallExpression -AssignmentExpression -> LeftHandSideExpression '=' Expression +ParenthesizedExpression -> '(' Expression ')' -LeftHandSideExpression -> MemberExpression - CallExpression -CallExpression -> MemberExpression Arguments (Arguments | '[' Expression ']' | '.' 'Ident')* -MemberExpression -> "Ident" ('[' Expression ']' | '.' 'Ident')* +ComplexLiteral -> ObjectLiteral | ArrayLiteral + +ObjectLiteral -> '{' PropertyDefinitionList '}' +PropertyDefinitionList -> (FieldProperty ';')* +FieldProperty -> "Ident" ':' Expression +ArrayLiteral -> '[' ElementList ']' +ElementList -> (Expression ',')* + +CallExpression -> MemberExpression Arguments +MemberExpression -> Var ('[' Expression ']' | '.' 'Ident')* Arguments -> '(' (Expression ',')* ')' WithExpression -> ConditionalExpression 'with' ObjectLiteral @@ -44,19 +54,14 @@ AddExpression -> MulExpression (('+'|'-') MulExpression)* MulExpression -> UnaryExpression (('*'|'/'|'%'|'++'|'::') UnaryExpression)* UnaryExpression -> LeftHandSideExpression | ('-'|'!'|'~') UnaryExpression -PrimaryExpression -> "Ident" | EnvVar | Literal | '(' Expression ')' | ObjectLiteral | ArrayLiteral +PrimaryExpression -> Var | Literal +Var -> "Ident" | EnvVar EnvVar -> '$' "Ident" Literal -> "NullLiteral" "IntegerLiteral" "FloatLiteral" "StringLiteral" "BooleanLiteral" -ObjectLiteral -> '{' PropertyDefinitionList '}' -PropertyDefinitionList -> (FieldProperty ';')* -FieldProperty -> "Ident" ':' Expression - -ArrayLiteral -> '[' ElementList ']' -ElementList -> (Expression ',')* IfStatement -> 'if' '(' Expression ')' Statement ('else' Statement)? diff --git a/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs b/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs index 0446b5e7..93f2f51d 100644 --- a/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs +++ b/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs @@ -5,6 +5,8 @@ namespace HydraScript.Infrastructure; public sealed partial class GeneratedRegexContainer : IGeneratedRegexContainer { - [GeneratedRegex(PatternContainer.Value, RegexOptions.Compiled)] + [GeneratedRegex( + PatternContainer.Value, + options: RegexOptions.Compiled | RegexOptions.ExplicitCapture)] public static partial Regex Regex { get; } } \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/ErrorPrograms/DefaultParameterTests.cs b/tests/HydraScript.IntegrationTests/ErrorPrograms/DefaultParameterTests.cs index debbb34e..1cd03c18 100644 --- a/tests/HydraScript.IntegrationTests/ErrorPrograms/DefaultParameterTests.cs +++ b/tests/HydraScript.IntegrationTests/ErrorPrograms/DefaultParameterTests.cs @@ -9,8 +9,7 @@ public void DefaultParameter_PlacedBeforeNamed_HydraScriptError() { const string script = "function func(a = 1, b: boolean) { }"; using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.HydraScriptError); + runner.Invoke().Should().Be(Executor.ExitCodes.HydraScriptError); fixture.LogMessages.Should() .Contain(x => x.Contains("The argument b: boolean of function func is placed after default value argument")); @@ -29,8 +28,7 @@ function f(a = 0, b = 1, c = 2) {} """ ; using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.HydraScriptError); + runner.Invoke().Should().Be(Executor.ExitCodes.HydraScriptError); const string expectedMessage = "Candidates are:\n" + "function f(number)\n" + diff --git a/tests/HydraScript.IntegrationTests/ErrorPrograms/FunctionWithoutReturnStatementTests.cs b/tests/HydraScript.IntegrationTests/ErrorPrograms/FunctionWithoutReturnStatementTests.cs index 935e167d..0d983051 100644 --- a/tests/HydraScript.IntegrationTests/ErrorPrograms/FunctionWithoutReturnStatementTests.cs +++ b/tests/HydraScript.IntegrationTests/ErrorPrograms/FunctionWithoutReturnStatementTests.cs @@ -15,8 +15,7 @@ function f(b: boolean) { } """; using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.HydraScriptError); + runner.Invoke().Should().Be(Executor.ExitCodes.HydraScriptError); fixture.LogMessages.Should() .Contain(x => x.Contains("function with non-void return type must have a return statement")); diff --git a/tests/HydraScript.IntegrationTests/ErrorPrograms/NullAssignmentWhenUndefinedTests.cs b/tests/HydraScript.IntegrationTests/ErrorPrograms/NullAssignmentWhenUndefinedTests.cs index 79bbf6c6..b863500c 100644 --- a/tests/HydraScript.IntegrationTests/ErrorPrograms/NullAssignmentWhenUndefinedTests.cs +++ b/tests/HydraScript.IntegrationTests/ErrorPrograms/NullAssignmentWhenUndefinedTests.cs @@ -8,8 +8,7 @@ public class NullAssignmentWhenUndefinedTests(TestHostFixture fixture) : IClassF public void NullAssignment_UndefinedDestinationOrReturnType_HydraScriptError(string script) { using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.HydraScriptError); + runner.Invoke().Should().Be(Executor.ExitCodes.HydraScriptError); fixture.LogMessages.Should() .Contain(x => x.Contains("Cannot assign 'null' when type is undefined")); } diff --git a/tests/HydraScript.IntegrationTests/ErrorPrograms/VariableInitializationTests.cs b/tests/HydraScript.IntegrationTests/ErrorPrograms/VariableInitializationTests.cs index 91370c99..04cf4444 100644 --- a/tests/HydraScript.IntegrationTests/ErrorPrograms/VariableInitializationTests.cs +++ b/tests/HydraScript.IntegrationTests/ErrorPrograms/VariableInitializationTests.cs @@ -8,8 +8,7 @@ public class VariableInitializationTests(TestHostFixture fixture) : IClassFixtur public void VariableWithoutTypeDeclared_AccessedBeforeInitialization_HydraScriptError(string script) { using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.HydraScriptError); + runner.Invoke().Should().Be(Executor.ExitCodes.HydraScriptError); fixture.LogMessages.Should() .Contain(x => x.Contains("Cannot access 'x' before initialization")); } diff --git a/tests/HydraScript.IntegrationTests/ErrorPrograms/VoidAssignmentTests.cs b/tests/HydraScript.IntegrationTests/ErrorPrograms/VoidAssignmentTests.cs index 96b26e4d..c1de85b2 100644 --- a/tests/HydraScript.IntegrationTests/ErrorPrograms/VoidAssignmentTests.cs +++ b/tests/HydraScript.IntegrationTests/ErrorPrograms/VoidAssignmentTests.cs @@ -17,8 +17,7 @@ function func(b: boolean) { let x = func(true) """; using var runner = fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.HydraScriptError); + runner.Invoke().Should().Be(Executor.ExitCodes.HydraScriptError); fixture.LogMessages.Should() .Contain(x => x.Contains("Cannot assign void")); } diff --git a/tests/HydraScript.IntegrationTests/Samples/compound_assign.js b/tests/HydraScript.IntegrationTests/Samples/compound_assign.js new file mode 100644 index 00000000..dc5891ff --- /dev/null +++ b/tests/HydraScript.IntegrationTests/Samples/compound_assign.js @@ -0,0 +1,19 @@ +let x = { + prop: 2; +} +x.prop += 4 + 5 * 3 +x.prop *= 7 +>>> x + +let y = 1 +y -= (y + 2) * (3 + 4) +y /= 5 +>>> y + +let arr: number[] = [] +let i = 0 +while (i < 5) { + arr ++= [i] + i += 1 +} +>>> arr \ No newline at end of file diff --git a/tests/HydraScript.IntegrationTests/SuccessPrograms/ArithmeticTests.cs b/tests/HydraScript.IntegrationTests/SuccessPrograms/ArithmeticTests.cs index f9889839..28f98cfb 100644 --- a/tests/HydraScript.IntegrationTests/SuccessPrograms/ArithmeticTests.cs +++ b/tests/HydraScript.IntegrationTests/SuccessPrograms/ArithmeticTests.cs @@ -24,8 +24,7 @@ public void Equality_AdditionToTheLeft_Success() using var runner = fixture.GetRunner( new TestHostFixture.Options( InMemoryScript: script)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.Success); + runner.Invoke().Should().Be(Executor.ExitCodes.Success); fixture.LogMessages.Should() .Contain(log => log.Contains("i is 5")); } diff --git a/tests/HydraScript.IntegrationTests/SuccessPrograms/DumpOptionTests.cs b/tests/HydraScript.IntegrationTests/SuccessPrograms/DumpOptionTests.cs index 7102853d..aec0d118 100644 --- a/tests/HydraScript.IntegrationTests/SuccessPrograms/DumpOptionTests.cs +++ b/tests/HydraScript.IntegrationTests/SuccessPrograms/DumpOptionTests.cs @@ -1,5 +1,6 @@ using System.IO.Abstractions; using HydraScript.Domain.BackEnd; +using HydraScript.Infrastructure; using Microsoft.Extensions.DependencyInjection; using NSubstitute; @@ -21,7 +22,7 @@ public void Invoke_DumpOptionPassed_FilesCreated() outputWriter.WriteLine(x.ArgAt(1)); }); - runner.Invoke(); + runner.Invoke().Should().Be(Executor.ExitCodes.Success); fileSystemMock.File.Received(1) .WriteAllText( Arg.Is(s => s.EndsWith(TestHostFixture.ScriptFileName + ".tokens")), diff --git a/tests/HydraScript.IntegrationTests/SuccessPrograms/InputTests.cs b/tests/HydraScript.IntegrationTests/SuccessPrograms/InputTests.cs index 10b82031..0bb251ec 100644 --- a/tests/HydraScript.IntegrationTests/SuccessPrograms/InputTests.cs +++ b/tests/HydraScript.IntegrationTests/SuccessPrograms/InputTests.cs @@ -1,4 +1,5 @@ using HydraScript.Domain.BackEnd; +using HydraScript.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using NSubstitute; @@ -22,7 +23,7 @@ public void Invoke_Input_Success() env.When(x => x.SetEnvironmentVariable("SOME_NUMBER", "1")) .Do(_ => env.GetEnvironmentVariable("SOME_NUMBER").Returns("1")); - runner.Invoke(); + runner.Invoke().Should().Be(Executor.ExitCodes.Success); console.Received(1).WriteLine("1"); } diff --git a/tests/HydraScript.IntegrationTests/SuccessPrograms/SuccessfulProgramsTests.cs b/tests/HydraScript.IntegrationTests/SuccessPrograms/SuccessfulProgramsTests.cs index 9cf78807..609055bd 100644 --- a/tests/HydraScript.IntegrationTests/SuccessPrograms/SuccessfulProgramsTests.cs +++ b/tests/HydraScript.IntegrationTests/SuccessPrograms/SuccessfulProgramsTests.cs @@ -13,8 +13,7 @@ public void Invoke_NoError_ReturnCodeIsZero(string relativePathToFile) FileName: relativePathToFile, MockFileSystem: false, MockEnv: false)); - var code = runner.Invoke(); - code.Should().Be(Executor.ExitCodes.Success); + runner.Invoke().Should().Be(Executor.ExitCodes.Success); } public class SuccessfulPrograms : TheoryData diff --git a/tests/HydraScript.UnitTests/Domain/FrontEnd/AstNodeTests.cs b/tests/HydraScript.UnitTests/Domain/FrontEnd/AstNodeTests.cs index b55c9664..d2bc9560 100644 --- a/tests/HydraScript.UnitTests/Domain/FrontEnd/AstNodeTests.cs +++ b/tests/HydraScript.UnitTests/Domain/FrontEnd/AstNodeTests.cs @@ -1,12 +1,16 @@ +using System.Text.RegularExpressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Declarations.AfterTypesAreLoaded; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions; +using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.AccessExpressions; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Expressions.PrimaryExpressions; using HydraScript.Domain.FrontEnd.Parser.Impl.Ast.Nodes.Statements; namespace HydraScript.UnitTests.Domain.FrontEnd; -public class AstNodeTests +public partial class AstNodeTests { [Fact] public void ChildOf_Precedence_Success() @@ -32,7 +36,44 @@ public void ChildOf_Precedence_Success() [Fact] public void IfStatement_ThenIsNotBlockAndElseIsNull_NotEmpty() { - var ifStatement = new IfStatement(Literal.Boolean(true), new InsideStatementJump("break")); + var ifStatement = new IfStatement( + Literal.Boolean(true), + new InsideStatementJump("break")); ifStatement.Empty.Should().BeFalse(); } + + [Fact] + public void Clone_MemberExpressionWithChain_ReturnsDeepCopy() + { + // obj.arr[0].x + var id = new IdentifierReference("obj"); + var dotArr = new DotAccess(new IdentifierReference("arr")); + var arrIndex = new IndexAccess(Literal.Number(0), dotArr); + var dotX = new DotAccess(new IdentifierReference("x"), arrIndex); + + var accessChain = new LinkedList( + [ + dotArr, + arrIndex, + dotX + ]); + var member = new MemberExpression(id, accessChain); + + var clone = member.Clone(); + + Assert.NotSame(member, clone); + Assert.NotSame(member.Id, clone.Id); + Assert.NotSame(member.AccessChain, clone.AccessChain); + + var memberAst = new AbstractSyntaxTree(member).ToString(); + var cloneAst = new AbstractSyntaxTree(clone).ToString(); + + Assert.NotEqual(memberAst, cloneAst); + Assert.Equal( + RemoveHashCodeDigits.Replace(memberAst, string.Empty), + RemoveHashCodeDigits.Replace(cloneAst, string.Empty)); + } + + [GeneratedRegex("[0-9]+")] + private static partial Regex RemoveHashCodeDigits { get; } } \ No newline at end of file diff --git a/tests/coverage-exclude.xml b/tests/coverage-exclude.xml index d5160dda..9af13767 100644 --- a/tests/coverage-exclude.xml +++ b/tests/coverage-exclude.xml @@ -7,6 +7,8 @@ .*ToString.* .*GetHashCode.* .*GetEnumerator.* + .*Clone.* + .*DeepClone.*