diff --git a/src/Panther.SyntaxGen/Syntax.xml b/src/Panther.SyntaxGen/Syntax.xml index 8f3d4f6..22ef74e 100644 --- a/src/Panther.SyntaxGen/Syntax.xml +++ b/src/Panther.SyntaxGen/Syntax.xml @@ -184,6 +184,12 @@ + + + + + + diff --git a/src/Panther.SyntaxGen/Typed.xml b/src/Panther.SyntaxGen/Typed.xml index 1f315aa..f156df0 100644 --- a/src/Panther.SyntaxGen/Typed.xml +++ b/src/Panther.SyntaxGen/Typed.xml @@ -143,6 +143,10 @@ + + + + diff --git a/src/Panther/CodeAnalysis/Emit/Emitter.cs b/src/Panther/CodeAnalysis/Emit/Emitter.cs index 048e167..a803ce9 100644 --- a/src/Panther/CodeAnalysis/Emit/Emitter.cs +++ b/src/Panther/CodeAnalysis/Emit/Emitter.cs @@ -669,6 +669,10 @@ private void EmitExpression(ILProcessor ilProcessor, TypedExpression expression) EmitNewExpression(ilProcessor, newExpression); break; + case TypedNullExpression nullExpression: + EmitNullExpression(ilProcessor, nullExpression); + break; + case TypedIndexExpression indexExpression: EmitIndexExpression(ilProcessor, indexExpression); break; @@ -826,6 +830,11 @@ TypedConstant constant } } + private void EmitNullExpression(ILProcessor ilProcessor, TypedNullExpression nullExpression) + { + ilProcessor.Emit(OpCodes.Ldnull); + } + private void EmitBinaryExpression( ILProcessor ilProcessor, TypedBinaryExpression binaryExpression @@ -1052,6 +1061,10 @@ TypedConversionExpression conversionExpression ilProcessor.Emit(OpCodes.Call, _convertObjectToString); return; } + + // no conversion needed + if (fromType == Type.Null) + return; } else if (toType == Type.Any) { @@ -1073,7 +1086,7 @@ TypedConversionExpression conversionExpression return; } - if (fromType == Type.String) + if (fromType == Type.String || fromType == Type.Null) { // no conversion required return; diff --git a/src/Panther/CodeAnalysis/Symbols/Type.cs b/src/Panther/CodeAnalysis/Symbols/Type.cs index 260f1ad..f16e3ab 100644 --- a/src/Panther/CodeAnalysis/Symbols/Type.cs +++ b/src/Panther/CodeAnalysis/Symbols/Type.cs @@ -17,6 +17,7 @@ protected Type(Symbol symbol) public static readonly Type Any = new TypeConstructor("any", TypeSymbol.Any); public static readonly Type Unit = new TypeConstructor("unit", TypeSymbol.Unit); + public static readonly Type Null = new TypeConstructor("null", TypeSymbol.Unit); public static readonly Type Bool = new TypeConstructor("bool", TypeSymbol.Bool); public static readonly Type Int = new TypeConstructor("int", TypeSymbol.Int); diff --git a/src/Panther/CodeAnalysis/Syntax/Parser.cs b/src/Panther/CodeAnalysis/Syntax/Parser.cs index 0e57d54..47f1280 100644 --- a/src/Panther/CodeAnalysis/Syntax/Parser.cs +++ b/src/Panther/CodeAnalysis/Syntax/Parser.cs @@ -66,6 +66,7 @@ public Parser(SourceFile sourceFile) _prefixParseFunctions[SyntaxKind.IfKeyword] = ParseIfExpression; _prefixParseFunctions[SyntaxKind.NumberToken] = ParseLiteralExpression; _prefixParseFunctions[SyntaxKind.NewKeyword] = ParseNewExpression; + _prefixParseFunctions[SyntaxKind.NullKeyword] = ParseNull; _prefixParseFunctions[SyntaxKind.OpenBraceToken] = ParseBlockExpression; _prefixParseFunctions[SyntaxKind.OpenParenToken] = ParseGroupOrUnitExpression; _prefixParseFunctions[SyntaxKind.StringToken] = ParseLiteralExpression; @@ -737,6 +738,12 @@ private ThisExpressionSyntax ParseThis() return new ThisExpressionSyntax(_sourceFile, ident); } + private ExpressionSyntax ParseNull() + { + var token = Accept(); + return new NullExpressionSyntax(_sourceFile, token); + } + private LiteralExpressionSyntax ParseBooleanLiteral() { var value = CurrentKind == SyntaxKind.TrueKeyword; diff --git a/src/Panther/CodeAnalysis/Syntax/Syntax.g.cs b/src/Panther/CodeAnalysis/Syntax/Syntax.g.cs index c5da88c..f9a0d5f 100644 --- a/src/Panther/CodeAnalysis/Syntax/Syntax.g.cs +++ b/src/Panther/CodeAnalysis/Syntax/Syntax.g.cs @@ -619,6 +619,32 @@ public override string ToString() public override TResult Accept(SyntaxVisitor visitor) => visitor.VisitThisExpression(this); } + public sealed partial record NullExpressionSyntax(SourceFile SourceFile, SyntaxToken NullToken) + : ExpressionSyntax(SourceFile) { + public override SyntaxKind Kind => SyntaxKind.NullExpression; + + public override int GetHashCode() + { + return HashCode.Combine(NullToken); + } + + public override IEnumerable GetChildren() + { + yield return NullToken; + } + + public override string ToString() + { + using var writer = new StringWriter(); + this.WriteTo(writer); + return writer.ToString(); + } + + public override void Accept(SyntaxVisitor visitor) => visitor.VisitNullExpression(this); + + public override TResult Accept(SyntaxVisitor visitor) => visitor.VisitNullExpression(this); + } + public sealed partial record WhileExpressionSyntax(SourceFile SourceFile, SyntaxToken WhileKeyword, SyntaxToken OpenParenToken, ExpressionSyntax ConditionExpression, SyntaxToken CloseParenToken, ExpressionSyntax Body) : ExpressionSyntax(SourceFile) { public override SyntaxKind Kind => SyntaxKind.WhileExpression; diff --git a/src/Panther/CodeAnalysis/Syntax/SyntaxFacts.cs b/src/Panther/CodeAnalysis/Syntax/SyntaxFacts.cs index bde71ab..d413c82 100644 --- a/src/Panther/CodeAnalysis/Syntax/SyntaxFacts.cs +++ b/src/Panther/CodeAnalysis/Syntax/SyntaxFacts.cs @@ -86,6 +86,7 @@ public static SyntaxKind GetKeywordKind(string span) "implicit" => SyntaxKind.ImplicitKeyword, "namespace" => SyntaxKind.NamespaceKeyword, "new" => SyntaxKind.NewKeyword, + "null" => SyntaxKind.NullKeyword, "object" => SyntaxKind.ObjectKeyword, "static" => SyntaxKind.StaticKeyword, "this" => SyntaxKind.ThisKeyword, @@ -131,6 +132,7 @@ public static SyntaxKind GetKeywordKind(string span) SyntaxKind.LessThanToken => "<", SyntaxKind.NamespaceKeyword => "namespace", SyntaxKind.NewKeyword => "new", + SyntaxKind.NullKeyword => "null", SyntaxKind.ObjectKeyword => "object", SyntaxKind.OpenBraceToken => "{", SyntaxKind.OpenBracketToken => "[", diff --git a/src/Panther/CodeAnalysis/Syntax/SyntaxKind.cs b/src/Panther/CodeAnalysis/Syntax/SyntaxKind.cs index 5ee8097..ce0910e 100644 --- a/src/Panther/CodeAnalysis/Syntax/SyntaxKind.cs +++ b/src/Panther/CodeAnalysis/Syntax/SyntaxKind.cs @@ -31,6 +31,7 @@ public enum SyntaxKind ImplicitKeyword, NamespaceKeyword, NewKeyword, + NullKeyword, ObjectKeyword, StaticKeyword, ThisKeyword, @@ -127,6 +128,7 @@ public enum SyntaxKind LiteralExpression, MemberAccessExpression, NewExpression, + NullExpression, ThisExpression, UnaryExpression, UnitExpression, diff --git a/src/Panther/CodeAnalysis/Syntax/SyntaxVisitor.g.cs b/src/Panther/CodeAnalysis/Syntax/SyntaxVisitor.g.cs index b80d869..300b628 100644 --- a/src/Panther/CodeAnalysis/Syntax/SyntaxVisitor.g.cs +++ b/src/Panther/CodeAnalysis/Syntax/SyntaxVisitor.g.cs @@ -104,6 +104,9 @@ public virtual void VisitNewExpression(NewExpressionSyntax node) => public virtual void VisitNoOperandInstruction(NoOperandInstructionSyntax node) => this.DefaultVisit(node); + public virtual void VisitNullExpression(NullExpressionSyntax node) => + this.DefaultVisit(node); + public virtual void VisitObjectDeclaration(ObjectDeclarationSyntax node) => this.DefaultVisit(node); @@ -244,6 +247,9 @@ public virtual TResult VisitNewExpression(NewExpressionSyntax node) => public virtual TResult VisitNoOperandInstruction(NoOperandInstructionSyntax node) => this.DefaultVisit(node); + public virtual TResult VisitNullExpression(NullExpressionSyntax node) => + this.DefaultVisit(node); + public virtual TResult VisitObjectDeclaration(ObjectDeclarationSyntax node) => this.DefaultVisit(node); diff --git a/src/Panther/CodeAnalysis/Typing/Conversion.cs b/src/Panther/CodeAnalysis/Typing/Conversion.cs index a74e731..968074d 100644 --- a/src/Panther/CodeAnalysis/Typing/Conversion.cs +++ b/src/Panther/CodeAnalysis/Typing/Conversion.cs @@ -38,6 +38,9 @@ public static Conversion Classify(Type from, Type to) if (from == Type.Int && to == Type.Char) return Explicit; + if (from == Type.Null && !IsValueType(to)) + return Implicit; + if ((from == Type.Bool || from == Type.Int || from == Type.Char) && to == Type.String) return Explicit; @@ -53,4 +56,6 @@ from is ArrayType(_, var fromElement) return None; } + + private static bool IsValueType(Type type) => type == Type.Bool || type == Type.Int || type == Type.Char; } diff --git a/src/Panther/CodeAnalysis/Typing/TypedNodeKind.cs b/src/Panther/CodeAnalysis/Typing/TypedNodeKind.cs index a7ecdaa..d037b8d 100644 --- a/src/Panther/CodeAnalysis/Typing/TypedNodeKind.cs +++ b/src/Panther/CodeAnalysis/Typing/TypedNodeKind.cs @@ -19,6 +19,7 @@ internal enum TypedNodeKind MethodExpression, NamespaceExpression, NewExpression, + NullExpression, PropertyExpression, TypeExpression, UnaryExpression, @@ -42,5 +43,5 @@ internal enum TypedNodeKind // Declarations ClassDeclaration, FunctionDeclaration, - ObjectDeclaration + ObjectDeclaration, } diff --git a/src/Panther/CodeAnalysis/Typing/TypedNodeVisitor.g.cs b/src/Panther/CodeAnalysis/Typing/TypedNodeVisitor.g.cs index 8975d2b..ea9c69b 100644 --- a/src/Panther/CodeAnalysis/Typing/TypedNodeVisitor.g.cs +++ b/src/Panther/CodeAnalysis/Typing/TypedNodeVisitor.g.cs @@ -83,6 +83,9 @@ public virtual void VisitNewExpression(TypedNewExpression node) => public virtual void VisitNopStatement(TypedNopStatement node) => this.DefaultVisit(node); + public virtual void VisitNullExpression(TypedNullExpression node) => + this.DefaultVisit(node); + public virtual void VisitPropertyExpression(TypedPropertyExpression node) => this.DefaultVisit(node); @@ -178,6 +181,9 @@ public virtual TResult VisitNewExpression(TypedNewExpression node) => public virtual TResult VisitNopStatement(TypedNopStatement node) => this.DefaultVisit(node); + public virtual TResult VisitNullExpression(TypedNullExpression node) => + this.DefaultVisit(node); + public virtual TResult VisitPropertyExpression(TypedPropertyExpression node) => this.DefaultVisit(node); diff --git a/src/Panther/CodeAnalysis/Typing/TypedNodes.cs b/src/Panther/CodeAnalysis/Typing/TypedNodes.cs index 2eaa11a..3ca7e40 100644 --- a/src/Panther/CodeAnalysis/Typing/TypedNodes.cs +++ b/src/Panther/CodeAnalysis/Typing/TypedNodes.cs @@ -91,3 +91,9 @@ internal partial record TypedVariableExpression { public override Type Type { get; init; } = Variable.Type; } + + +internal partial record TypedNullExpression +{ + public override Type Type { get; init; } = Type.Null; +} diff --git a/src/Panther/CodeAnalysis/Typing/TypedTreeRewriter.cs b/src/Panther/CodeAnalysis/Typing/TypedTreeRewriter.cs index f8adad5..b508364 100644 --- a/src/Panther/CodeAnalysis/Typing/TypedTreeRewriter.cs +++ b/src/Panther/CodeAnalysis/Typing/TypedTreeRewriter.cs @@ -32,7 +32,7 @@ TypedLiteralExpression boundLiteralExpression => RewriteLiteralExpression(boundLiteralExpression), TypedNewExpression boundLiteralExpression => RewriteNewExpression(boundLiteralExpression), - + TypedNullExpression boundNullExpression => RewriteNullExpression(boundNullExpression), TypedPropertyExpression boundPropertyExpression => RewritePropertyExpression(boundPropertyExpression), TypedTypeExpression boundTypeExpression => RewriteTypeExpression(boundTypeExpression), @@ -43,7 +43,7 @@ TypedVariableExpression boundVariableExpression => RewriteVariableExpression(boundVariableExpression), TypedWhileExpression boundWhileExpression => RewriteWhileExpression(boundWhileExpression), - _ => throw new ArgumentOutOfRangeException(nameof(node)) + _ => throw new ArgumentOutOfRangeException(nameof(node), node.GetType().FullName) }; protected virtual TypedExpression RewriteArrayCreationExpression( @@ -86,6 +86,8 @@ TypedArrayCreationExpression node ); } + protected virtual TypedExpression RewriteNullExpression(TypedNullExpression node) => node; + protected virtual TypedExpression RewriteFieldExpression(TypedFieldExpression node) => node; protected virtual TypedExpression RewriteTypeExpression(TypedTypeExpression node) => node; diff --git a/src/Panther/CodeAnalysis/Typing/Typer.cs b/src/Panther/CodeAnalysis/Typing/Typer.cs index 051dca7..0f670dd 100644 --- a/src/Panther/CodeAnalysis/Typing/Typer.cs +++ b/src/Panther/CodeAnalysis/Typing/Typer.cs @@ -951,6 +951,7 @@ ThisExpressionSyntax thisExpressionSyntax => BindThisExpression(thisExpressionSyntax, scope), NewExpressionSyntax newExpressionSyntax => BindNewExpression(newExpressionSyntax, scope), + NullExpressionSyntax nullExpressionSyntax => BindNullExpression(nullExpressionSyntax, scope), UnaryExpressionSyntax unaryExpressionSyntax => BindUnaryExpression(unaryExpressionSyntax, scope), UnitExpressionSyntax unit => BindUnitExpression(unit), @@ -1901,6 +1902,9 @@ string @namespace return symbol; } + private TypedExpression BindNullExpression(NullExpressionSyntax expr, TypedScope scope) => + new TypedNullExpression(expr); + private TypedExpression BindThisExpression(ThisExpressionSyntax syntax, TypedScope scope) { var symbol = scope.Symbol; diff --git a/src/Panther/CodeAnalysis/Typing/Typing.g.cs b/src/Panther/CodeAnalysis/Typing/Typing.g.cs index e000eb2..55969e0 100644 --- a/src/Panther/CodeAnalysis/Typing/Typing.g.cs +++ b/src/Panther/CodeAnalysis/Typing/Typing.g.cs @@ -321,6 +321,22 @@ public override string ToString() public override TResult Accept(TypedNodeVisitor visitor) => visitor.VisitVariableExpression(this); } +internal sealed partial record TypedNullExpression(SyntaxNode Syntax) + : TypedExpression(Syntax) { + public override TypedNodeKind Kind => TypedNodeKind.NullExpression; + + public override string ToString() + { + using var writer = new StringWriter(); + this.WriteTo(writer); + return writer.ToString(); + } + + public override void Accept(TypedNodeVisitor visitor) => visitor.VisitNullExpression(this); + + public override TResult Accept(TypedNodeVisitor visitor) => visitor.VisitNullExpression(this); +} + internal sealed partial record TypedAssignmentStatement(SyntaxNode Syntax, TypedExpression Left, TypedExpression Right) : TypedStatement(Syntax) { public override TypedNodeKind Kind => TypedNodeKind.AssignmentStatement; diff --git a/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/expected.il b/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/expected.il new file mode 100644 index 0000000..1f00b4b --- /dev/null +++ b/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/expected.il @@ -0,0 +1,38 @@ +// IL code: Nulls +.class private auto ansi '' +{ +} // end of class + +.class public auto ansi sealed $Program + extends [System.Private.CoreLib]System.Object +{ + .field public static string y + .field public static string x + + .method public static + void main () cil managed + { + // Method begins at RVA 0x2070 + // Code size 19 (0x13) + .maxstack 1 + .entrypoint + .locals init ( + [0] object + ) + + IL_0000: ldnull + IL_0001: stsfld string $Program::y + IL_0006: ldstr "test" + IL_000b: stsfld string $Program::x + IL_0010: ldnull + IL_0011: stsfld string $Program::x + IL_0016: ldsfld string $Program::x + IL_001b: stloc.0 + IL_001c: ldloc.0 + IL_001d: call void [Panther.StdLib]Panther.Predef::println(object) + IL_0022: ret + } // end of method $Program::main + +} // end of class $Program + + diff --git a/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/main.pn b/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/main.pn new file mode 100644 index 0000000..8c9b43f --- /dev/null +++ b/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/main.pn @@ -0,0 +1,4 @@ +val y : string = null +var x : string = "test" +x = null +println(x) \ No newline at end of file diff --git a/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/output.txt b/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/output.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/Panther.Tests/CodeAnalysis/Emit/Nulls/output.txt @@ -0,0 +1 @@ + diff --git a/tests/Panther.Tests/CodeAnalysis/Syntax/TokenGenerators.cs b/tests/Panther.Tests/CodeAnalysis/Syntax/TokenGenerators.cs index 13222b9..9c96993 100644 --- a/tests/Panther.Tests/CodeAnalysis/Syntax/TokenGenerators.cs +++ b/tests/Panther.Tests/CodeAnalysis/Syntax/TokenGenerators.cs @@ -37,6 +37,7 @@ public static Arbitrary NonSeparatorTokenTestData() = new NonSeparatorTokenTestData(SyntaxKind.NumberToken, "1"), new NonSeparatorTokenTestData(SyntaxKind.NumberToken, "123"), new NonSeparatorTokenTestData(SyntaxKind.NumberToken, "0"), + new NonSeparatorTokenTestData(SyntaxKind.NullKeyword, "null"), }.Concat( Enum.GetValues(typeof(SyntaxKind)) .Cast()