...
This commit is contained in:
@@ -4,7 +4,7 @@ import c
|
|||||||
// Test2
|
// Test2
|
||||||
// Test3
|
// Test3
|
||||||
// Test4
|
// Test4
|
||||||
global func main(args: f []string) {
|
global func main(args: []string) {
|
||||||
i = 0
|
i = 0
|
||||||
printf("%d\n", args.count)
|
printf("%d\n", args.count)
|
||||||
while i < args.count {
|
while i < args.count {
|
||||||
@@ -12,4 +12,6 @@ global func main(args: f []string) {
|
|||||||
printf("%s\n", args[i])
|
printf("%s\n", args[i])
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i: string = "test"
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/compiler/Nub.Lang/Frontend/DiagnosticsResult.cs
Normal file
21
src/compiler/Nub.Lang/Frontend/DiagnosticsResult.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Nub.Lang.Frontend.Diagnostics;
|
||||||
|
|
||||||
|
namespace Nub.Lang.Frontend;
|
||||||
|
|
||||||
|
public class DiagnosticsResult(List<Diagnostic> diagnostics)
|
||||||
|
{
|
||||||
|
public bool HasErrors => diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error);
|
||||||
|
|
||||||
|
public void PrintAllDiagnostics()
|
||||||
|
{
|
||||||
|
foreach (var diagnostic in diagnostics)
|
||||||
|
{
|
||||||
|
Console.WriteLine(diagnostic.Format());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiagnosticsResult<TResult>(List<Diagnostic> diagnostics, TResult value) : DiagnosticsResult(diagnostics)
|
||||||
|
{
|
||||||
|
public TResult Value { get; } = value;
|
||||||
|
}
|
||||||
@@ -10,9 +10,7 @@ public class Parser
|
|||||||
private int _index;
|
private int _index;
|
||||||
private List<Diagnostic> _diagnostics = [];
|
private List<Diagnostic> _diagnostics = [];
|
||||||
|
|
||||||
public IReadOnlyList<Diagnostic> Diagnostics => _diagnostics;
|
public DiagnosticsResult<ModuleNode> ParseModule(List<Token> tokens, string rootFilePath)
|
||||||
|
|
||||||
public ModuleNode ParseModule(List<Token> tokens, string rootFilePath)
|
|
||||||
{
|
{
|
||||||
_index = 0;
|
_index = 0;
|
||||||
_tokens = tokens;
|
_tokens = tokens;
|
||||||
@@ -42,7 +40,7 @@ public class Parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModuleNode(GetTokensForNode(0), rootFilePath, imports, definitions);
|
return new DiagnosticsResult<ModuleNode>(_diagnostics, new ModuleNode(GetTokensForNode(0), rootFilePath, imports, definitions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefinitionNode ParseDefinition()
|
private DefinitionNode ParseDefinition()
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
using Nub.Lang.Frontend.Parsing;
|
using Nub.Lang.Frontend.Parsing;
|
||||||
|
using Nub.Lang.Frontend.Diagnostics;
|
||||||
|
|
||||||
namespace Nub.Lang.Frontend.Typing;
|
namespace Nub.Lang.Frontend.Typing;
|
||||||
|
|
||||||
public class TypeCheckingException : Exception
|
|
||||||
{
|
|
||||||
public TypeCheckingException(string message) : base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TypeChecker
|
public class TypeChecker
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, NubType> _variables = new();
|
private readonly Dictionary<string, NubType> _variables = new();
|
||||||
private readonly List<DefinitionNode> _definitions;
|
private readonly List<DefinitionNode> _definitions;
|
||||||
|
private readonly List<Diagnostic> _diagnostics = [];
|
||||||
private NubType? _currentFunctionReturnType;
|
private NubType? _currentFunctionReturnType;
|
||||||
private bool _hasReturnStatement;
|
private bool _hasReturnStatement;
|
||||||
|
|
||||||
@@ -21,8 +16,10 @@ public class TypeChecker
|
|||||||
_definitions = definitions;
|
_definitions = definitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TypeCheck()
|
public DiagnosticsResult TypeCheck()
|
||||||
{
|
{
|
||||||
|
_diagnostics.Clear();
|
||||||
|
|
||||||
foreach (var structDef in _definitions.OfType<StructDefinitionNode>())
|
foreach (var structDef in _definitions.OfType<StructDefinitionNode>())
|
||||||
{
|
{
|
||||||
TypeCheckStructDef(structDef);
|
TypeCheckStructDef(structDef);
|
||||||
@@ -32,6 +29,8 @@ public class TypeChecker
|
|||||||
{
|
{
|
||||||
TypeCheckFuncDef(funcDef);
|
TypeCheckFuncDef(funcDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new DiagnosticsResult(_diagnostics.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TypeCheckStructDef(StructDefinitionNode structDef)
|
private void TypeCheckStructDef(StructDefinitionNode structDef)
|
||||||
@@ -41,14 +40,16 @@ public class TypeChecker
|
|||||||
{
|
{
|
||||||
if (fields.ContainsKey(field.Name))
|
if (fields.ContainsKey(field.Name))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Duplicate field '{field.Name}' in struct '{structDef.Name}'");
|
ReportError($"Duplicate field '{field.Name}' in struct '{structDef.Name}'", structDef);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.Value.HasValue)
|
if (field.Value.HasValue)
|
||||||
{
|
{
|
||||||
if (!TypeCheckExpression(field.Value.Value).Equals(field.Type))
|
var fieldType = TypeCheckExpression(field.Value.Value);
|
||||||
|
if (fieldType != null && !fieldType.Equals(field.Type))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException("Default field initializer does not match the defined type");
|
ReportError("Default field initializer does not match the defined type", field.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ public class TypeChecker
|
|||||||
|
|
||||||
if (_currentFunctionReturnType != null && !_hasReturnStatement)
|
if (_currentFunctionReturnType != null && !_hasReturnStatement)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Function '{funcDef.Name}' must return a value of type '{_currentFunctionReturnType}'");
|
ReportError($"Function '{funcDef.Name}' must return a value of type '{_currentFunctionReturnType}'", funcDef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ public class TypeChecker
|
|||||||
TypeCheckVariableAssignment(varAssign);
|
TypeCheckVariableAssignment(varAssign);
|
||||||
break;
|
break;
|
||||||
case FuncCallStatementNode funcCall:
|
case FuncCallStatementNode funcCall:
|
||||||
TypeCheckFuncCall(funcCall.FuncCall);
|
TypeCheckFuncCall(funcCall.FuncCall, funcCall);
|
||||||
break;
|
break;
|
||||||
case IfNode ifNode:
|
case IfNode ifNode:
|
||||||
TypeCheckIf(ifNode);
|
TypeCheckIf(ifNode);
|
||||||
@@ -106,42 +107,67 @@ public class TypeChecker
|
|||||||
case ContinueNode:
|
case ContinueNode:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new TypeCheckingException($"Unsupported statement type: {statement.GetType().Name}");
|
ReportError($"Unsupported statement type: {statement.GetType().Name}", statement);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TypeCheckVariableAssignment(VariableAssignmentNode varAssign)
|
private void TypeCheckVariableAssignment(VariableAssignmentNode varAssign)
|
||||||
{
|
{
|
||||||
var valueType = TypeCheckExpression(varAssign.Value);
|
var valueType = TypeCheckExpression(varAssign.Value);
|
||||||
|
if (valueType == null) return;
|
||||||
|
|
||||||
if (varAssign.ExplicitType.HasValue)
|
if (_variables.TryGetValue(varAssign.Name, out var existingVariable))
|
||||||
{
|
{
|
||||||
var explicitType = varAssign.ExplicitType.Value;
|
if (varAssign.ExplicitType.HasValue)
|
||||||
if (!AreTypesCompatible(valueType, explicitType))
|
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{explicitType}'");
|
if (!AreTypesCompatible(existingVariable, varAssign.ExplicitType.Value))
|
||||||
|
{
|
||||||
|
ReportError($"Explicit type '{varAssign.ExplicitType.Value}' on variable '{varAssign.Name}' is not compatible with declared type '{existingVariable}'", varAssign);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AreTypesCompatible(valueType, existingVariable))
|
||||||
|
{
|
||||||
|
ReportError($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{existingVariable}'", varAssign);
|
||||||
}
|
}
|
||||||
|
|
||||||
_variables[varAssign.Name] = explicitType;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_variables[varAssign.Name] = valueType;
|
if (varAssign.ExplicitType.HasValue)
|
||||||
|
{
|
||||||
|
var explicitType = varAssign.ExplicitType.Value;
|
||||||
|
if (!AreTypesCompatible(valueType, explicitType))
|
||||||
|
{
|
||||||
|
ReportError($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{explicitType}'", varAssign);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_variables[varAssign.Name] = explicitType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_variables[varAssign.Name] = valueType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckDereference(DereferenceNode dereference)
|
private NubType? TypeCheckDereference(DereferenceNode dereference)
|
||||||
{
|
{
|
||||||
TypeCheckExpression(dereference.Expression);
|
var exprType = TypeCheckExpression(dereference.Expression);
|
||||||
if (dereference.Expression.Type is not NubPointerType nubPointerType)
|
if (exprType == null) return null;
|
||||||
|
|
||||||
|
if (exprType is not NubPointerType nubPointerType)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Cannot dereference a non-pointer type {dereference.Expression.Type}");
|
ReportError($"Cannot dereference a non-pointer type {exprType}", dereference);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nubPointerType.BaseType;
|
return nubPointerType.BaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckFuncCall(FuncCall funcCall)
|
private NubType? TypeCheckFuncCall(FuncCall funcCall, Node node)
|
||||||
{
|
{
|
||||||
var localFuncDef = _definitions.OfType<LocalFuncDefinitionNode>().FirstOrDefault(f => f.Name == funcCall.Name);
|
var localFuncDef = _definitions.OfType<LocalFuncDefinitionNode>().FirstOrDefault(f => f.Name == funcCall.Name);
|
||||||
var externFuncDef = _definitions.OfType<ExternFuncDefinitionNode>().FirstOrDefault(f => f.Name == funcCall.Name);
|
var externFuncDef = _definitions.OfType<ExternFuncDefinitionNode>().FirstOrDefault(f => f.Name == funcCall.Name);
|
||||||
@@ -158,20 +184,22 @@ public class TypeChecker
|
|||||||
parameters = externFuncDef.Parameters;
|
parameters = externFuncDef.Parameters;
|
||||||
returnType = externFuncDef.ReturnType;
|
returnType = externFuncDef.ReturnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Function '{funcCall.Name}' is not defined");
|
ReportError($"Function '{funcCall.Name}' is not defined", node);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.Take(parameters.Count - 1).Any(x => x.Variadic))
|
if (parameters.Take(parameters.Count - 1).Any(x => x.Variadic))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Function '{funcCall.Name}' has multiple variadic parameters");
|
ReportError($"Function '{funcCall.Name}' has multiple variadic parameters", node);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < funcCall.Parameters.Count; i++)
|
for (var i = 0; i < funcCall.Parameters.Count; i++)
|
||||||
{
|
{
|
||||||
var argType = TypeCheckExpression(funcCall.Parameters[i]);
|
var argType = TypeCheckExpression(funcCall.Parameters[i]);
|
||||||
|
if (argType == null) continue;
|
||||||
|
|
||||||
NubType paramType;
|
NubType paramType;
|
||||||
if (i < parameters.Count)
|
if (i < parameters.Count)
|
||||||
@@ -180,16 +208,17 @@ public class TypeChecker
|
|||||||
}
|
}
|
||||||
else if (parameters.LastOrDefault()?.Variadic ?? false)
|
else if (parameters.LastOrDefault()?.Variadic ?? false)
|
||||||
{
|
{
|
||||||
return parameters[^1].Type;
|
paramType = parameters[^1].Type;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Function '{funcCall.Name}' does not take {funcCall.Parameters.Count} parameters");
|
ReportError($"Function '{funcCall.Name}' does not take {funcCall.Parameters.Count} parameters", node);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AreTypesCompatible(argType, paramType))
|
if (!AreTypesCompatible(argType, paramType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Parameter {i} of function '{funcCall.Name}' expects type '{paramType}', but got '{argType}'");
|
ReportError($"Parameter {i + 1} of function '{funcCall.Name}' expects type '{paramType}', but got '{argType}'", funcCall.Parameters[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,9 +228,9 @@ public class TypeChecker
|
|||||||
private void TypeCheckIf(IfNode ifNode)
|
private void TypeCheckIf(IfNode ifNode)
|
||||||
{
|
{
|
||||||
var conditionType = TypeCheckExpression(ifNode.Condition);
|
var conditionType = TypeCheckExpression(ifNode.Condition);
|
||||||
if (!conditionType.Equals(NubPrimitiveType.Bool))
|
if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"If condition must be a boolean expression, got '{conditionType}'");
|
ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeCheckBlock(ifNode.Body);
|
TypeCheckBlock(ifNode.Body);
|
||||||
@@ -216,9 +245,9 @@ public class TypeChecker
|
|||||||
private void TypeCheckWhile(WhileNode whileNode)
|
private void TypeCheckWhile(WhileNode whileNode)
|
||||||
{
|
{
|
||||||
var conditionType = TypeCheckExpression(whileNode.Condition);
|
var conditionType = TypeCheckExpression(whileNode.Condition);
|
||||||
if (!conditionType.Equals(NubPrimitiveType.Bool))
|
if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"While condition must be a boolean expression, got '{conditionType}'");
|
ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeCheckBlock(whileNode.Body);
|
TypeCheckBlock(whileNode.Body);
|
||||||
@@ -231,24 +260,26 @@ public class TypeChecker
|
|||||||
if (returnNode.Value.HasValue)
|
if (returnNode.Value.HasValue)
|
||||||
{
|
{
|
||||||
var returnType = TypeCheckExpression(returnNode.Value.Value);
|
var returnType = TypeCheckExpression(returnNode.Value.Value);
|
||||||
|
if (returnType == null) return;
|
||||||
|
|
||||||
if (_currentFunctionReturnType == null)
|
if (_currentFunctionReturnType == null)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException("Cannot return a value from a function with no return type");
|
ReportError("Cannot return a value from a function with no return type", returnNode.Value.Value);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AreTypesCompatible(returnType, _currentFunctionReturnType))
|
if (!AreTypesCompatible(returnType, _currentFunctionReturnType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Return value of type '{returnType}' is not compatible with function return type '{_currentFunctionReturnType}'");
|
ReportError($"Return value of type '{returnType}' is not compatible with function return type '{_currentFunctionReturnType}'", returnNode.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_currentFunctionReturnType != null)
|
else if (_currentFunctionReturnType != null)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Function must return a value of type '{_currentFunctionReturnType}'");
|
ReportError($"Function must return a value of type '{_currentFunctionReturnType}'", returnNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckExpression(ExpressionNode expression)
|
private NubType? TypeCheckExpression(ExpressionNode expression)
|
||||||
{
|
{
|
||||||
var resultType = expression switch
|
var resultType = expression switch
|
||||||
{
|
{
|
||||||
@@ -259,63 +290,83 @@ public class TypeChecker
|
|||||||
BinaryExpressionNode binaryExpr => TypeCheckBinaryExpression(binaryExpr),
|
BinaryExpressionNode binaryExpr => TypeCheckBinaryExpression(binaryExpr),
|
||||||
CastNode cast => TypeCheckCast(cast),
|
CastNode cast => TypeCheckCast(cast),
|
||||||
DereferenceNode dereference => TypeCheckDereference(dereference),
|
DereferenceNode dereference => TypeCheckDereference(dereference),
|
||||||
FuncCallExpressionNode funcCallExpr => TypeCheckFuncCall(funcCallExpr.FuncCall),
|
FuncCallExpressionNode funcCallExpr => TypeCheckFuncCall(funcCallExpr.FuncCall, funcCallExpr),
|
||||||
StructInitializerNode structInit => TypeCheckStructInitializer(structInit),
|
StructInitializerNode structInit => TypeCheckStructInitializer(structInit),
|
||||||
UnaryExpressionNode unaryExpression => TypeCheckUnaryExpression(unaryExpression),
|
UnaryExpressionNode unaryExpression => TypeCheckUnaryExpression(unaryExpression),
|
||||||
MemberAccessNode memberAccess => TypeCheckMemberAccess(memberAccess),
|
MemberAccessNode memberAccess => TypeCheckMemberAccess(memberAccess),
|
||||||
_ => throw new TypeCheckingException($"Unsupported expression type: {expression.GetType().Name}")
|
_ => ReportUnsupportedExpression(expression)
|
||||||
};
|
};
|
||||||
|
|
||||||
expression.Type = resultType;
|
if (resultType != null)
|
||||||
|
{
|
||||||
|
expression.Type = resultType;
|
||||||
|
}
|
||||||
|
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckArrayIndex(ArrayIndexNode arrayIndex)
|
private NubType? ReportUnsupportedExpression(ExpressionNode expression)
|
||||||
|
{
|
||||||
|
ReportError($"Unsupported expression type: {expression.GetType().Name}", expression);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubType? TypeCheckArrayIndex(ArrayIndexNode arrayIndex)
|
||||||
{
|
{
|
||||||
var expressionType = TypeCheckExpression(arrayIndex.Expression);
|
var expressionType = TypeCheckExpression(arrayIndex.Expression);
|
||||||
|
if (expressionType == null) return null;
|
||||||
|
|
||||||
if (expressionType is not NubArrayType arrayType)
|
if (expressionType is not NubArrayType arrayType)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Annot access index of non-array type {expressionType}");
|
ReportError($"Cannot access index of non-array type {expressionType}", arrayIndex.Expression);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexType = TypeCheckExpression(arrayIndex.Index);
|
var indexType = TypeCheckExpression(arrayIndex.Index);
|
||||||
if (!IsInteger(indexType))
|
if (indexType != null && !IsInteger(indexType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException("Array index type must be an integer");
|
ReportError("Array index type must be an integer", arrayIndex.Index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return arrayType.BaseType;
|
return arrayType.BaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckIdentifier(IdentifierNode identifier)
|
private NubType? TypeCheckIdentifier(IdentifierNode identifier)
|
||||||
{
|
{
|
||||||
if (!_variables.TryGetValue(identifier.Identifier, out var varType))
|
if (!_variables.TryGetValue(identifier.Identifier, out var varType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Variable '{identifier.Identifier}' is not defined");
|
ReportError($"Variable '{identifier.Identifier}' is not defined", identifier);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return varType;
|
return varType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckAddressOf(AddressOfNode addressOf)
|
private NubType? TypeCheckAddressOf(AddressOfNode addressOf)
|
||||||
{
|
{
|
||||||
TypeCheckExpression(addressOf.Expression);
|
var exprType = TypeCheckExpression(addressOf.Expression);
|
||||||
|
if (exprType == null) return null;
|
||||||
|
|
||||||
if (addressOf.Expression is not (IdentifierNode or MemberAccessNode))
|
if (addressOf.Expression is not (IdentifierNode or MemberAccessNode))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Cannot take the address of {addressOf.Expression.Type}");
|
ReportError($"Cannot take the address of {exprType}", addressOf.Expression);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NubPointerType(addressOf.Expression.Type);
|
return new NubPointerType(exprType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckBinaryExpression(BinaryExpressionNode binaryExpr)
|
private NubType? TypeCheckBinaryExpression(BinaryExpressionNode binaryExpr)
|
||||||
{
|
{
|
||||||
var leftType = TypeCheckExpression(binaryExpr.Left);
|
var leftType = TypeCheckExpression(binaryExpr.Left);
|
||||||
var rightType = TypeCheckExpression(binaryExpr.Right);
|
var rightType = TypeCheckExpression(binaryExpr.Right);
|
||||||
|
|
||||||
|
if (leftType == null || rightType == null) return null;
|
||||||
|
|
||||||
if (!leftType.Equals(rightType))
|
if (!leftType.Equals(rightType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Left '{leftType}' and right '{rightType}' side of the binary expression is not equal");
|
ReportError($"Left '{leftType}' and right '{rightType}' side of the binary expression must be the same type", binaryExpr);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (binaryExpr.Operator)
|
switch (binaryExpr.Operator)
|
||||||
@@ -329,7 +380,8 @@ public class TypeChecker
|
|||||||
case BinaryExpressionOperator.LessThanOrEqual:
|
case BinaryExpressionOperator.LessThanOrEqual:
|
||||||
if (!IsNumeric(leftType))
|
if (!IsNumeric(leftType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Comparison operators require numeric operands, got '{leftType}' and '{rightType}'");
|
ReportError($"Comparison operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NubPrimitiveType.Bool;
|
return NubPrimitiveType.Bool;
|
||||||
@@ -339,36 +391,40 @@ public class TypeChecker
|
|||||||
case BinaryExpressionOperator.Divide:
|
case BinaryExpressionOperator.Divide:
|
||||||
if (!IsNumeric(leftType))
|
if (!IsNumeric(leftType))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Arithmetic operators require numeric operands, got '{leftType}' and '{rightType}'");
|
ReportError($"Arithmetic operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return leftType;
|
return leftType;
|
||||||
default:
|
default:
|
||||||
throw new TypeCheckingException($"Unsupported binary operator: {binaryExpr.Operator}");
|
ReportError($"Unsupported binary operator: {binaryExpr.Operator}", binaryExpr);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckCast(CastNode cast)
|
private NubType? TypeCheckCast(CastNode cast)
|
||||||
{
|
{
|
||||||
TypeCheckExpression(cast.Expression);
|
TypeCheckExpression(cast.Expression);
|
||||||
// TODO: Check if castable
|
// TODO: Check if castable
|
||||||
return cast.TargetType;
|
return cast.TargetType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckStructInitializer(StructInitializerNode structInit)
|
private NubType? TypeCheckStructInitializer(StructInitializerNode structInit)
|
||||||
{
|
{
|
||||||
var initialized = new HashSet<string>();
|
var initialized = new HashSet<string>();
|
||||||
|
|
||||||
var structType = structInit.StructType;
|
var structType = structInit.StructType;
|
||||||
if (structType is not NubStructType customType)
|
if (structType is not NubStructType customType)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Type '{structType}' is not a struct type");
|
ReportError($"Type '{structType}' is not a struct type", structInit);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var definition = _definitions.OfType<StructDefinitionNode>().FirstOrDefault(s => s.Name == structInit.StructType.Name);
|
var definition = _definitions.OfType<StructDefinitionNode>().FirstOrDefault(s => s.Name == structInit.StructType.Name);
|
||||||
if (definition == null)
|
if (definition == null)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Struct type '{customType.Name}' is not defined");
|
ReportError($"Struct type '{customType.Name}' is not defined", structInit);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var initializer in structInit.Initializers)
|
foreach (var initializer in structInit.Initializers)
|
||||||
@@ -376,13 +432,14 @@ public class TypeChecker
|
|||||||
var definitionField = definition.Fields.FirstOrDefault(f => f.Name == initializer.Key);
|
var definitionField = definition.Fields.FirstOrDefault(f => f.Name == initializer.Key);
|
||||||
if (definitionField == null)
|
if (definitionField == null)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Field '{initializer.Key}' does not exist in struct '{customType.Name}'");
|
ReportError($"Field '{initializer.Key}' does not exist in struct '{customType.Name}'", initializer.Value);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var initializerType = TypeCheckExpression(initializer.Value);
|
var initializerType = TypeCheckExpression(initializer.Value);
|
||||||
if (!AreTypesCompatible(initializerType, definitionField.Type))
|
if (initializerType != null && !AreTypesCompatible(initializerType, definitionField.Type))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'");
|
ReportError($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'", initializer.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized.Add(initializer.Key);
|
initialized.Add(initializer.Key);
|
||||||
@@ -397,16 +454,17 @@ public class TypeChecker
|
|||||||
{
|
{
|
||||||
if (!initialized.Contains(field.Name))
|
if (!initialized.Contains(field.Name))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Struct field '{field.Name}' is not initialized on type '{customType.Name}'");
|
ReportError($"Struct field '{field.Name}' is not initialized on type '{customType.Name}'", structInit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return structType;
|
return structType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckUnaryExpression(UnaryExpressionNode unaryExpression)
|
private NubType? TypeCheckUnaryExpression(UnaryExpressionNode unaryExpression)
|
||||||
{
|
{
|
||||||
var operandType = TypeCheckExpression(unaryExpression.Operand);
|
var operandType = TypeCheckExpression(unaryExpression.Operand);
|
||||||
|
if (operandType == null) return null;
|
||||||
|
|
||||||
switch (unaryExpression.Operator)
|
switch (unaryExpression.Operator)
|
||||||
{
|
{
|
||||||
@@ -422,27 +480,32 @@ public class TypeChecker
|
|||||||
return operandType;
|
return operandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeCheckingException($"Cannot negate non-numeric type {operandType}");
|
ReportError($"Cannot negate non-numeric type {operandType}", unaryExpression.Operand);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
case UnaryExpressionOperator.Invert:
|
case UnaryExpressionOperator.Invert:
|
||||||
{
|
{
|
||||||
if (!operandType.Equals(NubPrimitiveType.Bool))
|
if (!operandType.Equals(NubPrimitiveType.Bool))
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Cannot invert non-boolean type {operandType}");
|
ReportError($"Cannot invert non-boolean type {operandType}", unaryExpression.Operand);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return operandType;
|
return operandType;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException();
|
ReportError($"Unsupported unary operator: {unaryExpression.Operator}", unaryExpression);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NubType TypeCheckMemberAccess(MemberAccessNode memberAccess)
|
private NubType? TypeCheckMemberAccess(MemberAccessNode memberAccess)
|
||||||
{
|
{
|
||||||
var expressionType = TypeCheckExpression(memberAccess.Expression);
|
var expressionType = TypeCheckExpression(memberAccess.Expression);
|
||||||
|
if (expressionType == null) return null;
|
||||||
|
|
||||||
switch (expressionType)
|
switch (expressionType)
|
||||||
{
|
{
|
||||||
case NubArrayType:
|
case NubArrayType:
|
||||||
@@ -459,20 +522,29 @@ public class TypeChecker
|
|||||||
var definition = _definitions.OfType<StructDefinitionNode>().FirstOrDefault(s => s.Name == structType.Name);
|
var definition = _definitions.OfType<StructDefinitionNode>().FirstOrDefault(s => s.Name == structType.Name);
|
||||||
if (definition == null)
|
if (definition == null)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Struct type '{structType.Name}' is not defined");
|
ReportError($"Struct type '{structType.Name}' is not defined", memberAccess);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var field = definition.Fields.FirstOrDefault(f => f.Name == memberAccess.Member);
|
var field = definition.Fields.FirstOrDefault(f => f.Name == memberAccess.Member);
|
||||||
if (field == null)
|
if (field == null)
|
||||||
{
|
{
|
||||||
throw new TypeCheckingException($"Field '{memberAccess.Member}' does not exist in struct '{structType.Name}'");
|
ReportError($"Field '{memberAccess.Member}' does not exist in struct '{structType.Name}'", memberAccess);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return field.Type;
|
return field.Type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeCheckingException($"Cannot access member '{memberAccess.Member}' on type '{expressionType}'");
|
ReportError($"Cannot access member '{memberAccess.Member}' on type '{expressionType}'", memberAccess);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReportError(string message, Node node)
|
||||||
|
{
|
||||||
|
var diagnostic = Diagnostic.Error(message).At(node).Build();
|
||||||
|
_diagnostics.Add(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AreTypesCompatible(NubType sourceType, NubType targetType)
|
private static bool AreTypesCompatible(NubType sourceType, NubType targetType)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
private static readonly Lexer Lexer = new();
|
private static readonly Lexer Lexer = new();
|
||||||
private static readonly Parser Parser = new();
|
private static readonly Parser Parser = new();
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length != 2)
|
if (args.Length != 2)
|
||||||
@@ -19,16 +19,16 @@ internal static class Program
|
|||||||
Console.WriteLine("Example: nub src out.asm");
|
Console.WriteLine("Example: nub src out.asm");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var input = Path.GetFullPath(args[0]);
|
var input = Path.GetFullPath(args[0]);
|
||||||
var output = Path.GetFullPath(args[1]);
|
var output = Path.GetFullPath(args[1]);
|
||||||
|
|
||||||
if (!Directory.Exists(input))
|
if (!Directory.Exists(input))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error: Input directory '{input}' does not exist.");
|
Console.WriteLine($"Error: Input directory '{input}' does not exist.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputDir = Path.GetDirectoryName(output);
|
var outputDir = Path.GetDirectoryName(output);
|
||||||
if (outputDir == null || !Directory.Exists(outputDir))
|
if (outputDir == null || !Directory.Exists(outputDir))
|
||||||
{
|
{
|
||||||
@@ -42,24 +42,25 @@ internal static class Program
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Diagnostic> diagnostics = [];
|
List<ModuleNode> modules;
|
||||||
|
try
|
||||||
var modules = RunFrontend(input, diagnostics);
|
|
||||||
|
|
||||||
foreach (var diagnostic in diagnostics)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine(diagnostic.Format());
|
modules = RunFrontend(input);
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
|
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var definitions = modules.SelectMany(f => f.Definitions).ToList();
|
var definitions = modules.SelectMany(f => f.Definitions).ToList();
|
||||||
|
|
||||||
var typeChecker = new TypeChecker(definitions);
|
var typeChecker = new TypeChecker(definitions);
|
||||||
typeChecker.TypeCheck();
|
var typeCheckResult = typeChecker.TypeCheck();
|
||||||
|
typeCheckResult.PrintAllDiagnostics();
|
||||||
|
if (typeCheckResult.HasErrors)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
var generator = new Generator(definitions);
|
var generator = new Generator(definitions);
|
||||||
var result = generator.Generate();
|
var result = generator.Generate();
|
||||||
@@ -68,14 +69,14 @@ internal static class Program
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ModuleNode> RunFrontend(string rootFilePath, List<Diagnostic> diagnostics)
|
private static List<ModuleNode> RunFrontend(string rootFilePath)
|
||||||
{
|
{
|
||||||
List<ModuleNode> modules = [];
|
List<ModuleNode> modules = [];
|
||||||
RunFrontend(rootFilePath, modules, diagnostics);
|
RunFrontend(rootFilePath, modules);
|
||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunFrontend(string rootFilePath, List<ModuleNode> modules, List<Diagnostic> diagnostics)
|
private static void RunFrontend(string rootFilePath, List<ModuleNode> modules)
|
||||||
{
|
{
|
||||||
var filePaths = Directory.EnumerateFiles(rootFilePath, "*.nub", SearchOption.TopDirectoryOnly);
|
var filePaths = Directory.EnumerateFiles(rootFilePath, "*.nub", SearchOption.TopDirectoryOnly);
|
||||||
|
|
||||||
@@ -86,16 +87,21 @@ internal static class Program
|
|||||||
tokens.AddRange(Lexer.Lex(src, new SourceFile(filePath, src)));
|
tokens.AddRange(Lexer.Lex(src, new SourceFile(filePath, src)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var module = Parser.ParseModule(tokens, rootFilePath);
|
var parseResult = Parser.ParseModule(tokens, rootFilePath);
|
||||||
diagnostics.AddRange(Parser.Diagnostics);
|
parseResult.PrintAllDiagnostics();
|
||||||
modules.Add(module);
|
if (parseResult.HasErrors)
|
||||||
|
|
||||||
foreach (var import in module.Imports)
|
|
||||||
{
|
{
|
||||||
var importPath = Path.GetFullPath(import, module.Path);
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
modules.Add(parseResult.Value);
|
||||||
|
|
||||||
|
foreach (var import in parseResult.Value.Imports)
|
||||||
|
{
|
||||||
|
var importPath = Path.GetFullPath(import, parseResult.Value.Path);
|
||||||
if (modules.All(m => m.Path != importPath))
|
if (modules.All(m => m.Path != importPath))
|
||||||
{
|
{
|
||||||
RunFrontend(importPath, modules, diagnostics);
|
RunFrontend(importPath, modules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user