Skip to content

Commit

Permalink
(many) Add implicitly typed int arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
perlun committed Jun 12, 2024
1 parent d79372b commit de082f7
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 18 deletions.
4 changes: 3 additions & 1 deletion release-notes/v0.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
- Fix bug in C++ preprocessor parsing [[#481][481]]
- Add `-o` flag for overriding the compiled output path [[#482][482]]
- Add `--idempotent` flag [[#486][486]]
- Remove char_at kludge [[#493][493]]
- Remove `char_at()` kludge [[#493][493]]
- Add implicitly typed `int` arrays [[#494][494]]

### Changed
#### Data types
Expand Down Expand Up @@ -91,3 +92,4 @@
[491]: https://github.com/perlang-org/perlang/pull/491
[492]: https://github.com/perlang-org/perlang/pull/492
[493]: https://github.com/perlang-org/perlang/pull/493
[494]: https://github.com/perlang-org/perlang/pull/493
23 changes: 22 additions & 1 deletion src/Perlang.Common/Expr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public interface IVisitor<out TR>
TR VisitCallExpr(Call expr);
TR VisitIndexExpr(Index expr);
TR VisitGroupingExpr(Grouping expr);
TR VisitCollectionInitializerExpr(CollectionInitializer collectionInitializer);
TR VisitLiteralExpr(Literal expr);
TR VisitLogicalExpr(Logical expr);
TR VisitUnaryPrefixExpr(UnaryPrefix expr);
Expand Down Expand Up @@ -220,6 +221,24 @@ public override TR Accept<TR>(IVisitor<TR> visitor)
public Token? Token => (Expression as ITokenAware)?.Token;
}

public class CollectionInitializer : Expr, ITokenAware
{
public ImmutableList<Expr> Elements { get; }

public CollectionInitializer(List<Expr> elements, Token token)
{
Elements = elements.ToImmutableList();
Token = token;
}

public override TR Accept<TR>(IVisitor<TR> visitor)
{
return visitor.VisitCollectionInitializerExpr(this);
}

public Token Token { get; }
}

/// <summary>
/// A literal string or number.
/// </summary>
Expand Down Expand Up @@ -325,10 +344,12 @@ public override TR Accept<TR>(IVisitor<TR> visitor)
public class Identifier : Expr, ITokenAware
{
public Token Name { get; }
public bool IsCollection { get; }

public Identifier(Token name)
public Identifier(Token name, bool isCollection = false)
{
Name = name;
IsCollection = isCollection;
}

public override TR Accept<TR>(IVisitor<TR> visitor)
Expand Down
5 changes: 5 additions & 0 deletions src/Perlang.Common/ITypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public interface ITypeReference
/// </summary>
Type? ClrType { get; }

/// <summary>
/// Gets a value indicating whether this type represents an array type, e.g. `string[]` or `int[]`.
/// </summary>
bool? IsArray { get; }

/// <summary>
/// Gets the C++ type that this <see cref="ITypeReference"/> refers to.
/// </summary>
Expand Down
5 changes: 0 additions & 5 deletions src/Perlang.Common/Parameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ public class Parameter

public Token TypeSpecifier => TypeReference.TypeSpecifier;

public Parameter(ITypeReference typeReference)
{
TypeReference = typeReference;
}

public Parameter(Token name, ITypeReference typeReference)
{
Name = name;
Expand Down
31 changes: 26 additions & 5 deletions src/Perlang.Common/TypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ public void SetClrType(Type? value)
clrType = value;
}

public bool? IsArray => clrType?.IsArray;

public string CppType =>
clrType switch
{
// TODO: Add the other Perlang-supported types as well
null => throw new InvalidOperationException("Internal error: ClrType was unexpectedly null"),

// Value types
var t when t == typeof(Int32) => "int32_t",
var t when t == typeof(UInt32) => "uint32_t",
var t when t == typeof(Int64) => "int64_t",
Expand All @@ -55,8 +59,12 @@ public void SetClrType(Type? value)
var t when t == typeof(Double) => "double",
var t when t == typeof(bool) => "bool",
var t when t == typeof(void) => "void",

// Arrays of value types
var t when t == typeof(Int32[]) => "perlang::IntArray",

// Reference types
var t when t == typeof(BigInteger) => "BigInt",
null => throw new InvalidOperationException("Internal error: ClrType was unexpectedly null"),

// TODO: Handle UTF-8 strings here too
var t when t.FullName == "Perlang.Lang.AsciiString" => "perlang::ASCIIString",
Expand All @@ -68,7 +76,9 @@ public void SetClrType(Type? value)
public string PossiblyWrappedCppType =>
clrType switch
{
// TODO: Add the other Perlang-supported types as well
null => throw new InvalidOperationException("Internal error: ClrType was unexpectedly null"),

// Value types
var t when t == typeof(Int32) => "int32_t",
var t when t == typeof(UInt32) => "uint32_t",
var t when t == typeof(Int64) => "int64_t",
Expand All @@ -77,8 +87,14 @@ public void SetClrType(Type? value)
var t when t == typeof(Double) => "double",
var t when t == typeof(bool) => "bool",
var t when t == typeof(void) => "void",

// Arrays of value types
var t when t == typeof(Int32[]) => "std::shared_ptr<const perlang::IntArray>",

// Reference types. BigInt is the outlier here, since it's actually used as a value type. This could
// actually be one reason why the performance of using our BigInts are so bad; there may be implicit
// copying happening behind the scenes.
var t when t == typeof(BigInteger) => "BigInt",
null => throw new InvalidOperationException("Internal error: ClrType was unexpectedly null"),

// These are wrapped in std::shared_ptr<>, as a simple way to deal with ownership for now. For the
// long-term solution, see https://github.com/perlang-org/perlang/issues/378.
Expand All @@ -92,6 +108,9 @@ public void SetClrType(Type? value)
public bool CppWrapInSharedPtr =>
clrType switch
{
null => throw new InvalidOperationException("Internal error: ClrType was unexpectedly null"),

// Value types
var t when t == typeof(Int32) => false,
var t when t == typeof(UInt32) => false,
var t when t == typeof(Int64) => false,
Expand All @@ -101,7 +120,9 @@ public void SetClrType(Type? value)
var t when t == typeof(bool) => false,
var t when t == typeof(void) => false,
var t when t == typeof(BigInteger) => true,
null => throw new InvalidOperationException("Internal error: ClrType was unexpectedly null"),

// Arrays of value types
var t when t == typeof(Int32[]) => true,

// TODO: Handle UTF-8 strings here too
var t when t.FullName == "Perlang.Lang.AsciiString" => true,
Expand Down
9 changes: 9 additions & 0 deletions src/Perlang.Common/VisitorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ public virtual VoidObject VisitGroupingExpr(Expr.Grouping expr)
return VoidObject.Void;
}

public virtual VoidObject VisitCollectionInitializerExpr(Expr.CollectionInitializer collectionInitializer)
{
foreach (Expr element in collectionInitializer.Elements) {
Visit(element);
}

return VoidObject.Void;
}

/// <summary>
/// Visits a Literal expression. This method does not need to be called in child classes.
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions src/Perlang.Interpreter/Compiler/PerlangCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,36 @@ public object VisitAssignExpr(Expr.Assign expr)
return VoidObject.Void;
}

public object? VisitCollectionInitializerExpr(Expr.CollectionInitializer collectionInitializer)
{
currentMethod.Append($"std::make_shared<{collectionInitializer.TypeReference.CppType}>(");

// Collection initializers must be prepended with a type specifier, since the C++ compiler will not attempt
// to guess the correct type for us.
if (collectionInitializer.TypeReference.CppType == "perlang::IntArray") {
currentMethod.Append("std::initializer_list<int32_t> ");
}
else {
throw new PerlangCompilerException($"Type {collectionInitializer.TypeReference.ClrType.ToTypeKeyword()} is not supported for collection initializers");
}

currentMethod.Append('{');

for (int index = 0; index < collectionInitializer.Elements.Count; index++) {
Expr element = collectionInitializer.Elements[index];
element.Accept(this);

if (index < collectionInitializer.Elements.Count - 1) {
currentMethod.Append(", ");
}
}

currentMethod.Append('}');
currentMethod.Append(')');

return VoidObject.Void;
}

public object VisitLiteralExpr(Expr.Literal expr)
{
if (expr.Value is AsciiString)
Expand Down
5 changes: 5 additions & 0 deletions src/Perlang.Interpreter/Internals/AstPrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public string VisitGroupingExpr(Expr.Grouping expr)
return Parenthesize("group", expr.Expression);
}

public string VisitCollectionInitializerExpr(Expr.CollectionInitializer expr)
{
return Parenthesize("collection", expr.Elements.ToArray());
}

public string VisitLiteralExpr(Expr.Literal expr)
{
if (expr.Value == null)
Expand Down
2 changes: 1 addition & 1 deletion src/Perlang.Interpreter/NameResolution/NameResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ public override VoidObject VisitVarStmt(Stmt.Var stmt)
Resolve(stmt.Initializer);
}

Define(stmt.Name, stmt.TypeReference ?? new TypeReference(stmt.Name));
Define(stmt.Name, stmt.TypeReference);

return VoidObject.Void;
}
Expand Down
48 changes: 47 additions & 1 deletion src/Perlang.Interpreter/Typing/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,31 @@ public override VoidObject VisitIndexExpr(Expr.Index expr)
expr.TypeReference.SetClrType(valueType);
break;

case { } when type.IsArray:
Type elementType = type.GetElementType()!;

if (elementType != typeof(int)) {
typeValidationErrorCallback(new TypeValidationError(
expr.ClosingBracket,
$"Array of type '{elementType}' cannot be indexed'")
);
}

if (!argumentType.IsAssignableTo(typeof(int)))
{
typeValidationErrorCallback(new TypeValidationError(
expr.ClosingBracket,
$"Array of type '{elementType}' cannot be indexed by '{argumentType.ToTypeKeyword()}'")
);
}

expr.TypeReference.SetClrType(elementType);
break;

case { } when type == typeof(NullObject):
typeValidationErrorCallback(new TypeValidationError(
expr.ClosingBracket,
$"'null' reference cannot be indexed")
"'null' reference cannot be indexed")
);
break;

Expand All @@ -569,6 +590,31 @@ public override VoidObject VisitGroupingExpr(Expr.Grouping expr)
return VoidObject.Void;
}

public override VoidObject VisitCollectionInitializerExpr(Expr.CollectionInitializer expr)
{
base.VisitCollectionInitializerExpr(expr);

// In the future, the idea is to loosen this restriction and figure out the most specific base type instead,
// and use that as the inferred type of the collection initializer.
if (expr.Elements.Select(e => e.TypeReference.ClrType).Distinct().Count() > 1)
{
typeValidationErrorCallback(new TypeValidationError(
expr.Token,
"All elements in a collection initializer must have the same type")
);

return VoidObject.Void;
}

// Infer the type of the collection initializer from the first element, since we have now validated that all
// elements have the same type.
Type elementType = expr.Elements.First().TypeReference.ClrType!;
Type collectionType = elementType.MakeArrayType();
expr.TypeReference.SetClrType(collectionType);

return VoidObject.Void;
}

public override VoidObject VisitLiteralExpr(Expr.Literal expr)
{
base.VisitLiteralExpr(expr);
Expand Down
27 changes: 27 additions & 0 deletions src/Perlang.Parser/PerlangParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,14 @@ private Stmt VarDeclaration()
// Support optional typing on this form:
// var s: String;
Token typeSpecifier = null;

if (Match(COLON))
{
typeSpecifier = Consume(IDENTIFIER, "Expecting type name.");
}

Expr initializer = null;

if (Match(EQUAL))
{
initializer = Expression();
Expand All @@ -387,6 +389,7 @@ private Stmt VarDeclaration()
}

return new Stmt.Var(name, initializer, new TypeReference(typeSpecifier));

}

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / test (ubuntu-22.04)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-arm)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-arm)

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-arm)

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / build-website

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-arm64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-arm64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-arm64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-x64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-x64)

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (linux-x64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (osx-arm64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (osx-arm64)

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (osx-arm64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (osx-x64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (osx-x64)

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (osx-x64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (win-x64)

Check warning on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (win-x64)

Check failure on line 393 in src/Perlang.Parser/PerlangParser.cs

View workflow job for this annotation

GitHub Actions / publish-snapshot-packages (win-x64)


private Stmt WhileStatement()
Expand Down Expand Up @@ -795,6 +798,30 @@ private Expr Primary()
return new Expr.Grouping(expr);
}

if (Match(LEFT_SQUARE_BRACKET))
{
var startToken = Previous();
var elements = new List<Expr>();

while (!Peek().Type.Equals(RIGHT_SQUARE_BRACKET) && !IsAtEnd)
{
elements.Add(Expression());

if (Match(COMMA))
{
continue;
}
else
{
break;
}
}

Consume(RIGHT_SQUARE_BRACKET, "Expect ']' at end of collection initializer.");

return new Expr.CollectionInitializer(elements, startToken);
}

if (Check(SEMICOLON))
{
// Bare semicolon, no expression inside. To avoid having to handle pesky null exceptions all over
Expand Down
2 changes: 1 addition & 1 deletion src/Perlang.Parser/Scanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ private void Number()

// Note that numbers are not parsed at this stage. We deliberately postpone it to the parsing stage, to be
// able to conjoin MINUS and NUMBER tokens together for negative numbers. The previous approach (inherited
// from Lox) worked poorly with our idea of "narrowing down" constants to smallest possible integer. See
// from Lox) worked poorly with our idea of "narrowing down" constants to the smallest possible integer. See
// #302 for some more details.
AddToken(new NumericToken(source[start..current], line, numberCharacters, suffix, isFractional, numberBase, numberStyles));
}
Expand Down
2 changes: 1 addition & 1 deletion src/Perlang.Stdlib/Internal/MemoryAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static MemoryAllocator()
// TODO: some prior art.

// We keep a local track of all memory allocated using this allocator, for a poor-man's "garbage collection"
// when the process exits. In the future we may attempt to do better: #
// when the process exits. In the future we may attempt to do better: #378
void* result = NativeMemory.Alloc(count);
AllocatedChunks.Add((IntPtr)result);
return result;
Expand Down
Loading

0 comments on commit de082f7

Please sign in to comment.