Skip to content

Commit

Permalink
Use immutable collections in syntax nodes and other utility classes (#…
Browse files Browse the repository at this point in the history
…107)

* change lexer interfaces to use ImmutableArray instead of IReadOnlyList

* change TextLocation and SyntaxTrivia to readonly records

* changed all syntax nodes to use ImmutableArray

* change ClrTypeCache, ClrTypeCacheView and symbol classes to use ImmutableArrays
  • Loading branch information
ChrisKXu authored Aug 6, 2024
1 parent fc56b87 commit ccc91c3
Show file tree
Hide file tree
Showing 37 changed files with 825 additions and 849 deletions.
17 changes: 9 additions & 8 deletions src/Todl.Compiler.Tests/CodeAnalysis/LexerTests/LexerTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using FluentAssertions;
using Todl.Compiler.CodeAnalysis.Syntax;
Expand All @@ -13,13 +14,13 @@ public sealed partial class LexerTests
private SyntaxToken LexSingle(string text)
{
var tokens = Lex(text);
tokens.Count.Should().Be(2);
tokens.Length.Should().Be(2);
tokens[0].GetDiagnostics().Should().BeEmpty();

return tokens.First();
}

private IReadOnlyList<SyntaxToken> Lex(string text)
private ImmutableArray<SyntaxToken> Lex(string text)
{
var lexer = new Lexer() { SourceText = SourceText.FromString(text) };
lexer.Lex();
Expand All @@ -36,7 +37,7 @@ public void TestLexerBasics()
lexer.Lex();

var tokens = lexer.SyntaxTokens;
tokens.Count.Should().Be(6); // '1', '+', '2', '+', '3' and EndOfFileToken
tokens.Length.Should().Be(6); // '1', '+', '2', '+', '3' and EndOfFileToken
tokens.SelectMany(t => t.GetDiagnostics()).Should().BeEmpty();
}

Expand Down Expand Up @@ -152,11 +153,11 @@ public void TestSingleLineCommentSimple()
{
var text = "// comment";
var tokens = Lex(text);
tokens.Count.Should().Be(1); // eof
tokens.Length.Should().Be(1); // eof

var eof = tokens[0];
eof.Kind.Should().Be(SyntaxKind.EndOfFileToken);
eof.LeadingTrivia.Count.Should().Be(1);
eof.LeadingTrivia.Length.Should().Be(1);
eof.LeadingTrivia.Should().Contain(t => t.Kind == SyntaxKind.SingleLineCommentTrivia
&& t.Text.ToString() == text);
}
Expand All @@ -166,18 +167,18 @@ public void TestSingleLineCommentsWithTokens()
{
var text = "//A\nreturn 0; //B";
var tokens = Lex(text);
tokens.Count.Should().Be(4); // return, 0, ;, eof
tokens.Length.Should().Be(4); // return, 0, ;, eof

var returnToken = tokens[0];
returnToken.Kind.Should().Be(SyntaxKind.ReturnKeywordToken);
returnToken.LeadingTrivia.Count.Should().Be(2); // comment, line break
returnToken.LeadingTrivia.Length.Should().Be(2); // comment, line break

var a = returnToken.LeadingTrivia[0];
a.Kind.Should().Be(SyntaxKind.SingleLineCommentTrivia);
a.Text.ToString().Should().Be("//A");

var commaToken = tokens[2];
commaToken.TrailingTrivia.Count.Should().Be(2); // whitespace, comment
commaToken.TrailingTrivia.Length.Should().Be(2); // whitespace, comment

var b = commaToken.TrailingTrivia[1];
b.Kind.Should().Be(SyntaxKind.SingleLineCommentTrivia);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void ParseFunctionDeclarationMemberWithSingleParameter()
function.Should().NotBeNull();
function.Name.Text.Should().Be("Function");
function.ReturnType.Text.Should().Be("void");
function.Parameters.Items.Count.Should().Be(1);
function.Parameters.Items.Should().HaveCount(1);
function.Body.InnerStatements.Should().BeEmpty();

var a = function.Parameters.Items[0];
Expand All @@ -62,7 +62,7 @@ public void ParseFunctionDeclarationMemberWithMultipleParameters()
function.Should().NotBeNull();
function.Name.Text.Should().Be("Function");
function.ReturnType.Text.Should().Be("void");
function.Parameters.Items.Count.Should().Be(2);
function.Parameters.Items.Should().HaveCount(2);
function.Body.InnerStatements.Should().BeEmpty();

var a = function.Parameters.Items[0];
Expand All @@ -81,7 +81,7 @@ public void ParseFunctionDeclarationMemberWithArrayParameters()
function.Should().NotBeNull();
function.Name.Text.Should().Be("Function");
function.ReturnType.Text.Should().Be("void");
function.Parameters.Items.Count.Should().Be(2);
function.Parameters.Items.Should().HaveCount(2);
function.Body.InnerStatements.Should().BeEmpty();

var a = function.Parameters.Items[0];
Expand All @@ -102,7 +102,7 @@ public void ParseFunctionDeclarationMemberWithMultiDimensionalArrayParameters()
function.Should().NotBeNull();
function.Name.Text.Should().Be("Function");
function.ReturnType.Text.Should().Be("void");
function.Parameters.Items.Count.Should().Be(3);
function.Parameters.Items.Should().HaveCount(3);
function.Body.InnerStatements.Should().BeEmpty();

var intParameter = function.Parameters.Items[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ public void WhileUntilStatementsCanHaveOneOrMoreInnerStatements(string inputText
var whileUntilStatement = TestUtils.ParseStatement<WhileUntilStatement>(inputText);
whileUntilStatement.Should().NotBeNull();
whileUntilStatement.GetDiagnostics().Should().BeEmpty();
whileUntilStatement.BlockStatement.InnerStatements.Count.Should().Be(expectedStatementsCount);
whileUntilStatement.BlockStatement.InnerStatements.Should().HaveCount(expectedStatementsCount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private BoundClrFunctionCallExpression BindFunctionCallWithNamedArgumentsInterna
&& m.IsStatic == isStatic
&& !m.ContainsGenericParameters
&& m.IsPublic
&& m.GetParameters().Length == functionCallExpression.Arguments.Items.Count);
&& m.GetParameters().Length == functionCallExpression.Arguments.Items.Length);

var arguments = functionCallExpression.Arguments.Items.ToDictionary(
keySelector: a => a.Identifier.Value.Text.ToString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private BoundObjectCreationExpression BindNewExpressionWithNamedArgumentsInterna
var clrType = (targetType as ClrTypeSymbol).ClrType;
var arguments = newExpression.Arguments;
var candidates = clrType.GetConstructors()
.Where(c => c.IsPublic && c.GetParameters().Length == arguments.Items.Count);
.Where(c => c.IsPublic && c.GetParameters().Length == arguments.Items.Length);
var argumentsDictionary = arguments.Items.ToDictionary(
keySelector: a => a.Identifier.Value.Text.ToString(),
elementSelector: a => BindExpression(a.Expression));
Expand Down
18 changes: 10 additions & 8 deletions src/Todl.Compiler/CodeAnalysis/ClrTypeCache.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Todl.Compiler.CodeAnalysis.Symbols;
Expand All @@ -9,16 +10,17 @@ namespace Todl.Compiler.CodeAnalysis;

public sealed class ClrTypeCache
{
private readonly HashSet<string> loadedNamespaces = new();
private readonly ImmutableHashSet<string>.Builder loadedNamespaces
= ImmutableHashSet.CreateBuilder<string>();

public IReadOnlySet<Assembly> Assemblies { get; }
public ImmutableHashSet<Assembly> Assemblies { get; }
public Assembly CoreAssembly { get; } // the assembly that contains object, bool, int, etc...
public IReadOnlySet<ClrTypeSymbol> Types { get; }
public IReadOnlySet<string> Namespaces => loadedNamespaces;
public ImmutableHashSet<ClrTypeSymbol> Types { get; }
public ImmutableHashSet<string> Namespaces => loadedNamespaces.ToImmutable();

public BuiltInTypes BuiltInTypes { get; }

private static readonly IReadOnlyDictionary<string, SpecialType> builtInTypeNames
private static readonly ImmutableDictionary<string, SpecialType> builtInTypeNames
= new Dictionary<string, SpecialType>()
{
{ "bool", SpecialType.ClrBoolean },
Expand All @@ -45,19 +47,19 @@ private static readonly IReadOnlyDictionary<string, SpecialType> builtInTypeName
{ typeof(float).FullName, SpecialType.ClrFloat },
{ "double", SpecialType.ClrDouble },
{ typeof(double).FullName, SpecialType.ClrDouble }
};
}.ToImmutableDictionary();

private ClrTypeCache(IEnumerable<Assembly> assemblies, Assembly coreAssembly)
{
Assemblies = assemblies.ToHashSet();
Assemblies = assemblies.ToImmutableHashSet();
CoreAssembly = coreAssembly;

Types = assemblies
.SelectMany(a => a.GetExportedTypes())
.Where(t => !t.IsGenericType) // TODO: support generic type
.Where(t => !builtInTypeNames.ContainsKey(t.FullName))
.Select(t => new ClrTypeSymbol(t))
.ToHashSet();
.ToImmutableHashSet();

BuiltInTypes = new BuiltInTypes(this);

Expand Down
114 changes: 57 additions & 57 deletions src/Todl.Compiler/CodeAnalysis/ClrTypeCacheView.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,86 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Todl.Compiler.CodeAnalysis.Symbols;
using Todl.Compiler.CodeAnalysis.Syntax;

namespace Todl.Compiler.CodeAnalysis
namespace Todl.Compiler.CodeAnalysis;

public sealed class ClrTypeCacheView
{
public sealed class ClrTypeCacheView
private readonly ClrTypeCache clrTypeCache;
private readonly ImmutableDictionary<string, ClrTypeSymbol> typeAliases;

internal ClrTypeSymbol ResolveBaseType(string name)
{
private readonly ClrTypeCache clrTypeCache;
private readonly IDictionary<string, ClrTypeSymbol> typeAliases;
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}

internal ClrTypeSymbol ResolveBaseType(string name)
if (typeAliases.ContainsKey(name))
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
return typeAliases[name];
}

if (typeAliases.ContainsKey(name))
{
return typeAliases[name];
}
return clrTypeCache.Resolve(name);
}

return clrTypeCache.Resolve(name);
}
public ClrTypeSymbol ResolveType(NameExpression nameExpression)
=> ResolveBaseType(nameExpression.Text.ToString());

public ClrTypeSymbol ResolveType(NameExpression nameExpression)
=> ResolveBaseType(nameExpression.Text.ToString());
public ClrTypeSymbol ResolveType(TypeExpression typeExpression)
{
var baseType = ResolveType(typeExpression.BaseTypeExpression);
if (!typeExpression.IsArrayType)
{
return baseType;
}

public ClrTypeSymbol ResolveType(TypeExpression typeExpression)
if (baseType is null)
{
var baseType = ResolveType(typeExpression.BaseTypeExpression);
if (!typeExpression.IsArrayType)
{
return baseType;
}
return null;
}

if (baseType is null)
{
return null;
}
var resolvedTypeString = typeExpression.Text.ToString().Replace(typeExpression.BaseTypeExpression.Text.ToString(), baseType.Name);

var resolvedTypeString = typeExpression.Text.ToString().Replace(typeExpression.BaseTypeExpression.Text.ToString(), baseType.Name);
return new(baseType.ClrType.Assembly.GetType(resolvedTypeString));
}

return new(baseType.ClrType.Assembly.GetType(resolvedTypeString));
private ImmutableDictionary<string, ClrTypeSymbol> ImportTypeAliases(IEnumerable<ImportDirective> importDirectives)
{
if (importDirectives == null)
{
throw new ArgumentNullException(nameof(importDirectives));
}

private IDictionary<string, ClrTypeSymbol> ImportTypeAliases(IEnumerable<ImportDirective> importDirectives)
var importedTypes = importDirectives.SelectMany(importDirective =>
{
if (importDirectives == null)
{
throw new ArgumentNullException(nameof(importDirectives));
}
var importedNamespace = importDirective.Namespace.ToString();
var types = clrTypeCache
.Types
.Where(t => importedNamespace.Equals(t.Namespace));

var importedTypes = importDirectives.SelectMany(importDirective =>
if (!importDirective.ImportAll)
{
var importedNamespace = importDirective.Namespace.ToString();
var types = clrTypeCache
.Types
.Where(t => importedNamespace.Equals(t.Namespace));
var importedNames = importDirective
.ImportedNames
.Select(n => $"{importedNamespace}.{n}")
.ToHashSet();

if (!importDirective.ImportAll)
{
var importedNames = importDirective
.ImportedNames
.Select(n => $"{importedNamespace}.{n}")
.ToHashSet();

types = types.Where(t => importedNames.Contains(t.Name));
}
types = types.Where(t => importedNames.Contains(t.Name));
}

return types;
}).Distinct();
return types;
}).Distinct();

return importedTypes.ToDictionary(t => t.ClrType.Name);
}
return importedTypes.ToImmutableDictionary(t => t.ClrType.Name);
}

internal ClrTypeCacheView(ClrTypeCache cache, IEnumerable<ImportDirective> importDirectives)
{
clrTypeCache = cache;
typeAliases = ImportTypeAliases(importDirectives);
}
internal ClrTypeCacheView(ClrTypeCache cache, IEnumerable<ImportDirective> importDirectives)
{
clrTypeCache = cache;
typeAliases = ImportTypeAliases(importDirectives);
}
}
11 changes: 6 additions & 5 deletions src/Todl.Compiler/CodeAnalysis/Symbols/FunctionSymbol.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Todl.Compiler.CodeAnalysis.Syntax;

Expand All @@ -8,10 +9,10 @@ namespace Todl.Compiler.CodeAnalysis.Symbols;
public sealed class FunctionSymbol : Symbol
{
public FunctionDeclarationMember FunctionDeclarationMember { get; internal init; }
public IEnumerable<ParameterSymbol> Parameters { get; internal init; }
public ImmutableArray<ParameterSymbol> Parameters { get; internal init; }

public IEnumerable<string> OrderedParameterNames
=> FunctionDeclarationMember.Parameters.Items.Select(p => p.Identifier.Text.ToString());
public ImmutableArray<string> OrderedParameterNames
=> Parameters.Select(p => p.Name).ToImmutableArray();
public override string Name => FunctionDeclarationMember.Name.Text.ToString();
public TypeSymbol ReturnType
=> FunctionDeclarationMember.SyntaxTree.ClrTypeCacheView.ResolveType(FunctionDeclarationMember.ReturnType);
Expand All @@ -28,7 +29,7 @@ public static FunctionSymbol FromFunctionDeclarationMember(FunctionDeclarationMe
return new()
{
FunctionDeclarationMember = functionDeclarationMember,
Parameters = parameters
Parameters = parameters.ToImmutableArray()
};
}

Expand Down
13 changes: 5 additions & 8 deletions src/Todl.Compiler/CodeAnalysis/Symbols/VariableSymbol.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System;
namespace Todl.Compiler.CodeAnalysis.Symbols;

namespace Todl.Compiler.CodeAnalysis.Symbols
public abstract class VariableSymbol : Symbol
{
public abstract class VariableSymbol : Symbol
{
public abstract bool ReadOnly { get; }
public abstract TypeSymbol Type { get; }
public virtual bool Constant => false;
}
public abstract bool ReadOnly { get; }
public abstract TypeSymbol Type { get; }
public virtual bool Constant => false;
}
Loading

0 comments on commit ccc91c3

Please sign in to comment.