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()