Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(many) Add explicitly typed int[] arrays #496

Merged
merged 1 commit into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion release-notes/v0.5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- Add `--idempotent` flag [[#486][486]]
- Remove `char_at()` kludge [[#493][493]]
- Add implicitly typed `int` arrays [[#494][494]]
- Add explicitly typed `int[]` arrays [[#496][496]]

### Changed
#### Data types
Expand Down Expand Up @@ -92,4 +93,5 @@
[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
[494]: https://github.com/perlang-org/perlang/pull/494
[496]: https://github.com/perlang-org/perlang/pull/496
2 changes: 1 addition & 1 deletion src/Perlang.Common/Expr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public abstract class Expr

private Expr()
{
TypeReference = new TypeReference(typeSpecifier: null);
TypeReference = new TypeReference(typeSpecifier: null, isArray: false);
}

public interface IVisitor<out TR>
Expand Down
2 changes: 1 addition & 1 deletion src/Perlang.Common/ITypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface ITypeReference
/// <summary>
/// Gets a value indicating whether this type represents an array type, e.g. `string[]` or `int[]`.
/// </summary>
bool? IsArray { get; }
bool IsArray { get; }

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

public Token TypeSpecifier => TypeReference.TypeSpecifier;
public bool IsArray => TypeReference.IsArray;

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

public bool? IsArray => clrType?.IsArray;
private readonly bool isArray;

public bool IsArray => clrType?.IsArray ?? isArray;

public string CppType =>
clrType switch
Expand Down Expand Up @@ -136,9 +138,11 @@ public void SetClrType(Type? value)
/// specifier can be null, in which case type inference will be attempted.
/// </summary>
/// <param name="typeSpecifier">The token providing the type specifier (e.g. 'int' or 'string').</param>
public TypeReference(Token? typeSpecifier)
/// <param name="isArray">Whether the type is an array or not.</param>
public TypeReference(Token? typeSpecifier, bool isArray)
{
TypeSpecifier = typeSpecifier;
this.isArray = isArray;
}

/// <summary>
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 @@ -386,7 +386,7 @@ private void ResolveFunction(Stmt.Function function, FunctionType type)
foreach (Parameter param in function.Parameters)
{
Declare(param.Name);
Define(param.Name, new TypeReference(param.TypeSpecifier));
Define(param.Name, new TypeReference(param.TypeSpecifier, param.IsArray));
}

Resolve(function.Body);
Expand Down
25 changes: 16 additions & 9 deletions src/Perlang.Interpreter/Typing/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ public override VoidObject VisitVarStmt(Stmt.Var stmt)
return VoidObject.Void;
}

private static void ResolveExplicitTypes(ITypeReference typeReference)
private void ResolveExplicitTypes(ITypeReference typeReference)
{
if (typeReference.TypeSpecifier == null)
{
Expand All @@ -853,41 +853,48 @@ private static void ResolveExplicitTypes(ITypeReference typeReference)
// `Scanner.ReservedKeywordStrings` should be updated. (Adding unit tests for the new
// types in ReservedKeywordsTests is a good way to ensure this is not forgotten.)
case "int" or "Int32":
typeReference.SetClrType(typeof(int));
typeReference.SetClrType(typeReference.IsArray ? typeof(int[]) : typeof(int));
break;

case "long" or "Int64":
typeReference.SetClrType(typeof(long));
typeReference.SetClrType(typeReference.IsArray ? typeof(long[]) : typeof(long));
break;

case "bigint" or "BigInteger":
typeReference.SetClrType(typeof(BigInteger));
typeReference.SetClrType(typeReference.IsArray ? typeof(BigInteger[]) : typeof(BigInteger));
break;

case "uint" or "UInt32":
typeReference.SetClrType(typeof(uint));
typeReference.SetClrType(typeReference.IsArray ? typeof(uint[]) : typeof(uint));
break;

case "ulong" or "UInt64":
typeReference.SetClrType(typeof(ulong));
typeReference.SetClrType(typeReference.IsArray ? typeof(ulong[]) : typeof(ulong));
break;

// "Float" is called "Single" in C#/.NET, but Java uses `float` and `Float`. In this case, I think it
// makes little sense to make them inconsistent.
case "float" or "Float":
typeReference.SetClrType(typeof(float));
typeReference.SetClrType(typeReference.IsArray ? typeof(float[]) : typeof(float));
break;

case "double" or "Double":
typeReference.SetClrType(typeof(double));
typeReference.SetClrType(typeReference.IsArray ? typeof(double[]) : typeof(double));
break;

case "string" or "String":
// Perlang String is NOT the same as the .NET String class.
typeReference.SetClrType(typeof(Lang.String));
typeReference.SetClrType(typeReference.IsArray ? typeof(Lang.String[]) : typeof(Lang.String));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above adds support for many more array types than just int[], but only int[] (implicit and explicit) is supported right now. We'll add int[] and string[] as the two first ones, and the next ones will perhaps go into the next milestone.

break;

case "void":
if (typeReference.IsArray) {
typeValidationErrorCallback(new TypeValidationError(
typeReference.TypeSpecifier,
$"void arrays are not supported")
);
}

typeReference.SetClrType(typeof(void));
break;

Expand Down
47 changes: 44 additions & 3 deletions src/Perlang.Parser/PerlangParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,14 @@ private Stmt VarDeclaration()
// Support optional typing on this form:
// var s: String;
Token typeSpecifier = null;
bool isArray = false;

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

if (IsAtArray())
isArray = true;
}

Expr initializer = null;
Expand All @@ -388,7 +392,7 @@ private Stmt VarDeclaration()
Consume(SEMICOLON, "Expect ';' after variable declaration.");
}

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

private Stmt WhileStatement()
Expand Down Expand Up @@ -446,22 +450,27 @@ private Stmt.Function Function(string kind)
BlockReservedIdentifiers(parameterName);

Token parameterTypeSpecifier = null;
bool isArray = false;

// Parameters can optionally use a specific type. If the type is not provided, the compiler will
// try to infer the type based on the usage.
if (Match(COLON))
{
parameterTypeSpecifier = Consume(IDENTIFIER, "Expecting type name.");

if (IsAtArray())
isArray = true;
}

parameters.Add(new Parameter(parameterName, new TypeReference(parameterTypeSpecifier)));
parameters.Add(new Parameter(parameterName, new TypeReference(parameterTypeSpecifier, isArray)));
}
while (Match(COMMA));
}

Consume(RIGHT_PAREN, "Expect ')' after parameters.");

Token returnTypeSpecifier = null;
bool isReturnTypeArray = false;

if (Match(COLON))
{
Expand All @@ -477,13 +486,16 @@ private Stmt.Function Function(string kind)
else
{
returnTypeSpecifier = Consume(IDENTIFIER, "Expecting type name.");

if (IsAtArray())
isReturnTypeArray = true;
}
}

Consume(LEFT_BRACE, "Expect '{' before " + kind + " body.");
List<Stmt> body = Block();

return new Stmt.Function(name, parameters, body, new TypeReference(returnTypeSpecifier));
return new Stmt.Function(name, parameters, body, new TypeReference(returnTypeSpecifier, isReturnTypeArray));
}

private List<Stmt> Block()
Expand Down Expand Up @@ -905,6 +917,25 @@ private Token Advance()
private bool IsAtEnd =>
Peek().Type == EOF;

/// <summary>
/// Are we currently at an `[]` array specifier? (appendix used in type specifiers like `string[]` and so forth).
/// </summary>
/// <returns>`true` if we are currently at an `[]` specifier, `false` otherwise.</returns>
private bool IsAtArray()
{
if (Peek().Type == LEFT_SQUARE_BRACKET && PeekNext().Type == RIGHT_SQUARE_BRACKET) {
// Skip over the brackets
Advance();
Advance();

// This is a array definition, e.g. "a[]". We include this in the type reference.
return true;
}
else {
return false;
}
}

/// <summary>
/// Returns the token at the current position.
/// </summary>
Expand All @@ -915,6 +946,16 @@ private Token Peek()
return tokens[current];
}

private Token PeekNext()
{
if (tokens.Count > current + 1) {
return tokens[current + 1];
}
else {
throw new InvalidOperationException("No more tokens available");
}
}

/// <summary>
/// Returns the token right before the current position.
/// </summary>
Expand Down
43 changes: 41 additions & 2 deletions src/Perlang.Tests.Integration/Arrays/IntArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Perlang.Tests.Integration.Arrays;

public class IntArrayTests
{
[Fact(Skip = "Explicitly typed arrays are not yet supported")]
[Fact]
public void explicitly_typed_int_array_can_be_indexed()
{
string source = """
Expand Down Expand Up @@ -48,5 +48,44 @@ public void implicitly_typed_int_array_can_be_indexed()
);
}

// TODO: Add test for indexing before and after array size, ensuring that we get the expected exceptions
[Fact]
public void indexing_int_array_with_negative_index_produces_expected_error()
{
string source = """
var a = [1, 2, 3];

print a[-1];
""";

var result = EvalWithRuntimeErrorCatch(source);

result.Errors.Should()
.ContainSingle()
.Which
.Message.Should().Contain("exited with exit code 134");

result.OutputAsString.Should()
.Contain("index out of range (18446744073709551615 > 2)");
}

[Fact]
public void indexing_int_array_outside_of_boundaries_produces_expected_error()
{
string source = """
var a = [1, 2, 3];

// a[2] is the last element of the array
print a[3];
""";

var result = EvalWithRuntimeErrorCatch(source);

result.Errors.Should()
.ContainSingle()
.Which
.Message.Should().Contain("exited with exit code 134");

result.OutputAsString.Should()
.Contain("index out of range (3 > 2)");
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
2 changes: 1 addition & 1 deletion src/stdlib/src/int_array.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace perlang
int IntArray::operator[](size_t index) const
{
if (index >= length_) {
throw std::out_of_range("index out of range");
throw std::out_of_range("index out of range (" + std::to_string(index) + " > " + std::to_string(length_ - 1) + ")");
}

return arr_[index];
Expand Down
Loading