...
This commit is contained in:
@@ -118,7 +118,7 @@ public class Diagnostic
|
||||
var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
|
||||
|
||||
var numberPadding = contextEndLine.ToString().Length;
|
||||
var codePadding = lines.Skip(contextStartLine - 1).Take(contextEndLine - contextStartLine).Max(x => x.Length);
|
||||
var codePadding = lines.Skip(contextStartLine - 1).Take(contextEndLine - contextStartLine + 1).Max(x => x.Length);
|
||||
|
||||
sb.Append('╭');
|
||||
sb.Append(new string('─', numberPadding + 2));
|
||||
@@ -144,16 +144,16 @@ public class Diagnostic
|
||||
if (i >= startLine && i <= endLine)
|
||||
{
|
||||
var markerStartColumn = 1;
|
||||
var markerEndColumn = line.Length + 1;
|
||||
var markerEndColumn = line.Length;
|
||||
|
||||
if (i == startLine)
|
||||
{
|
||||
markerStartColumn = Span.Value.Start.Column;
|
||||
markerStartColumn = Math.Min(Span.Value.Start.Column, 1);
|
||||
}
|
||||
|
||||
if (i == endLine)
|
||||
{
|
||||
markerEndColumn = Span.Value.End.Column;
|
||||
markerEndColumn = Math.Min(Span.Value.End.Column, line.Length);
|
||||
}
|
||||
|
||||
var markerLength = markerEndColumn - markerStartColumn;
|
||||
|
||||
@@ -34,4 +34,12 @@ public class Module
|
||||
.Where(x => x.Exported || includePrivate)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<GlobalVariableSyntax> GlobalVariables(bool includePrivate)
|
||||
{
|
||||
return _definitions
|
||||
.OfType<GlobalVariableSyntax>()
|
||||
.Where(x => x.Exported || includePrivate)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,7 @@ public sealed class Parser
|
||||
{
|
||||
Symbol.Func => ParseFunc(startIndex, exported, null),
|
||||
Symbol.Struct => ParseStruct(startIndex, exported),
|
||||
Symbol.Let => ParseGlobalVariable(startIndex, exported),
|
||||
_ => throw new ParseException(Diagnostic
|
||||
.Error($"Expected 'func' or 'struct' but found '{keyword.Symbol}'")
|
||||
.WithHelp("Valid definition keywords are 'func' and 'struct'")
|
||||
@@ -113,6 +114,22 @@ public sealed class Parser
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private GlobalVariableSyntax ParseGlobalVariable(int startIndex, bool exported)
|
||||
{
|
||||
var name = ExpectIdentifier();
|
||||
|
||||
TypeSyntax? type = null;
|
||||
if (TryExpectSymbol(Symbol.Colon))
|
||||
{
|
||||
type = ParseType();
|
||||
}
|
||||
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
|
||||
return new GlobalVariableSyntax(GetTokens(startIndex), name.Value, exported, type, value);
|
||||
}
|
||||
|
||||
private FuncSignatureSyntax ParseFuncSignature()
|
||||
{
|
||||
var startIndex = _tokenIndex;
|
||||
|
||||
@@ -17,3 +17,5 @@ public record StructFuncSyntax(List<Token> Tokens, string Name, string? Hook, Fu
|
||||
public record StructSyntax(List<Token> Tokens, string Name, bool Exported, List<StructFieldSyntax> Fields, List<StructFuncSyntax> Functions) : DefinitionSyntax(Tokens, Name, Exported);
|
||||
|
||||
public record StructTemplateSyntax(List<Token> Tokens, List<string> TemplateArguments, string Name, bool Exported, List<StructFieldSyntax> Fields, List<StructFuncSyntax> Functions) : DefinitionSyntax(Tokens, Name, Exported);
|
||||
|
||||
public record GlobalVariableSyntax(List<Token> Tokens, string Name, bool Exported, TypeSyntax? ExplicitType, ExpressionSyntax Value) : DefinitionSyntax(Tokens, Name, Exported);
|
||||
@@ -5,6 +5,8 @@ namespace NubLang.Tokenization;
|
||||
public enum LiteralKind
|
||||
{
|
||||
Integer,
|
||||
Hex,
|
||||
Binary,
|
||||
Float,
|
||||
String,
|
||||
Bool
|
||||
|
||||
@@ -159,6 +159,51 @@ public sealed class Tokenizer
|
||||
var isFloat = false;
|
||||
var buffer = string.Empty;
|
||||
|
||||
if (current == '0' && Peek(1) is 'x')
|
||||
{
|
||||
buffer += "0x";
|
||||
Next();
|
||||
Next();
|
||||
while (Peek() != null && Uri.IsHexDigit(Peek()!.Value))
|
||||
{
|
||||
buffer += Peek()!.Value;
|
||||
Next();
|
||||
}
|
||||
|
||||
if (buffer.Length <= 2)
|
||||
{
|
||||
throw new TokenizerException(Diagnostic
|
||||
.Error("Invalid hex literal, no digits found")
|
||||
.At(_fileName, _line, _column)
|
||||
.Build());
|
||||
}
|
||||
|
||||
return new LiteralToken(_fileName, CreateSpan(lineStart, columnStart), LiteralKind.Hex, buffer);
|
||||
}
|
||||
|
||||
if (current == '0' && Peek(1) is 'b')
|
||||
{
|
||||
buffer += "0b";
|
||||
Next();
|
||||
Next();
|
||||
while (Peek() != null && (Peek() == '0' || Peek() == '1'))
|
||||
{
|
||||
buffer += Peek()!.Value;
|
||||
Next();
|
||||
}
|
||||
|
||||
if (buffer.Length <= 2)
|
||||
{
|
||||
throw new TokenizerException(Diagnostic
|
||||
.Error("Invalid binary literal, no digits found")
|
||||
.At(_fileName, _line, _column)
|
||||
.Build());
|
||||
}
|
||||
|
||||
return new LiteralToken(_fileName, CreateSpan(lineStart, columnStart), LiteralKind.Binary, buffer);
|
||||
}
|
||||
|
||||
buffer += current;
|
||||
while (Peek() != null)
|
||||
{
|
||||
var next = Peek()!.Value;
|
||||
@@ -187,7 +232,12 @@ public sealed class Tokenizer
|
||||
}
|
||||
}
|
||||
|
||||
return new LiteralToken(_fileName, CreateSpan(lineStart, columnStart), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
|
||||
return new LiteralToken(
|
||||
_fileName,
|
||||
CreateSpan(lineStart, columnStart),
|
||||
isFloat ? LiteralKind.Float : LiteralKind.Integer,
|
||||
buffer
|
||||
);
|
||||
}
|
||||
|
||||
if (current == '"')
|
||||
|
||||
@@ -12,4 +12,6 @@ public record StructFieldNode(string Name, NubType Type, ExpressionNode? Value)
|
||||
|
||||
public record StructFuncNode(string Name, string? Hook, FuncSignatureNode Signature, BlockNode Body) : Node;
|
||||
|
||||
public record StructNode(string Module, string Name, List<StructFieldNode> Fields, List<StructFuncNode> Functions) : DefinitionNode(Module, Name);
|
||||
public record StructNode(string Module, string Name, List<StructFieldNode> Fields, List<StructFuncNode> Functions) : DefinitionNode(Module, Name);
|
||||
|
||||
public record GlobalVariableNode(string Module, string Name, ExpressionNode Value) : DefinitionNode(Module, Name);
|
||||
@@ -46,27 +46,33 @@ public sealed class TypeChecker
|
||||
Definitions.Clear();
|
||||
ReferencedStructTypes.Clear();
|
||||
|
||||
foreach (var definition in _syntaxTree.Definitions)
|
||||
using (BeginRootScope(_syntaxTree.Metadata.ModuleName))
|
||||
{
|
||||
try
|
||||
foreach (var definition in _syntaxTree.Definitions)
|
||||
{
|
||||
switch (definition)
|
||||
try
|
||||
{
|
||||
case FuncSyntax funcSyntax:
|
||||
Definitions.Add(CheckFuncDefinition(funcSyntax));
|
||||
break;
|
||||
case StructSyntax structSyntax:
|
||||
Definitions.Add(CheckStructDefinition(structSyntax));
|
||||
break;
|
||||
case StructTemplateSyntax:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
switch (definition)
|
||||
{
|
||||
case FuncSyntax funcSyntax:
|
||||
Definitions.Add(CheckFuncDefinition(funcSyntax));
|
||||
break;
|
||||
case StructSyntax structSyntax:
|
||||
Definitions.Add(CheckStructDefinition(structSyntax));
|
||||
break;
|
||||
case GlobalVariableSyntax globalVariableSyntax:
|
||||
Definitions.Add(CheckGlobalVariable(globalVariableSyntax));
|
||||
break;
|
||||
case StructTemplateSyntax:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
catch (TypeCheckerException e)
|
||||
{
|
||||
Diagnostics.Add(e.Diagnostic);
|
||||
}
|
||||
}
|
||||
catch (TypeCheckerException e)
|
||||
{
|
||||
Diagnostics.Add(e.Diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,9 +91,41 @@ public sealed class TypeChecker
|
||||
return new ScopeDisposer(this);
|
||||
}
|
||||
|
||||
private ScopeDisposer BeginRootScope(string module)
|
||||
private ScopeDisposer BeginRootScope(string moduleName)
|
||||
{
|
||||
_scopes.Push(new Scope(module));
|
||||
if (!_visibleModules.TryGetValue(moduleName, out var moduleScope))
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Module with name {moduleName} not found").Build());
|
||||
}
|
||||
|
||||
var scope = new Scope(moduleName);
|
||||
_scopes.Push(scope);
|
||||
|
||||
foreach (var globalVariable in moduleScope.GlobalVariables(true))
|
||||
{
|
||||
NubType? type;
|
||||
|
||||
if (globalVariable.ExplicitType != null)
|
||||
{
|
||||
type = ResolveType(globalVariable.ExplicitType);
|
||||
var valueExpression = CheckExpression(globalVariable.Value, type);
|
||||
|
||||
if (valueExpression.Type != ResolveType(globalVariable.ExplicitType))
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error("Value does not match explicit type of global variable")
|
||||
.At(globalVariable.Value)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
type = CheckExpression(globalVariable.Value).Type;
|
||||
}
|
||||
|
||||
scope.DeclareVariable(new Variable(globalVariable.Name, type, VariableKind.LValue));
|
||||
}
|
||||
|
||||
return new ScopeDisposer(this);
|
||||
}
|
||||
|
||||
@@ -105,30 +143,27 @@ public sealed class TypeChecker
|
||||
|
||||
private StructNode CheckStructDefinition(StructSyntax node)
|
||||
{
|
||||
using (BeginRootScope(_syntaxTree.Metadata.ModuleName))
|
||||
{
|
||||
var fieldTypes = node.Fields
|
||||
.Select(x => new NubStructFieldType(x.Name, ResolveType(x.Type), x.Value != null))
|
||||
.ToList();
|
||||
var fieldTypes = node.Fields
|
||||
.Select(x => new NubStructFieldType(x.Name, ResolveType(x.Type), x.Value != null))
|
||||
.ToList();
|
||||
|
||||
var fieldFunctions = node.Functions
|
||||
.Select(x =>
|
||||
{
|
||||
var parameters = x.Signature.Parameters.Select(y => ResolveType(y.Type)).ToList();
|
||||
var returnType = ResolveType(x.Signature.ReturnType);
|
||||
return new NubStructFuncType(x.Name, x.Hook, parameters, returnType);
|
||||
})
|
||||
.ToList();
|
||||
var fieldFunctions = node.Functions
|
||||
.Select(x =>
|
||||
{
|
||||
var parameters = x.Signature.Parameters.Select(y => ResolveType(y.Type)).ToList();
|
||||
var returnType = ResolveType(x.Signature.ReturnType);
|
||||
return new NubStructFuncType(x.Name, x.Hook, parameters, returnType);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var structType = new NubStructType(CurrentScope.Module, node.Name, fieldTypes, fieldFunctions);
|
||||
var structType = new NubStructType(CurrentScope.Module, node.Name, fieldTypes, fieldFunctions);
|
||||
|
||||
CurrentScope.DeclareVariable(new Variable("this", structType, VariableKind.RValue));
|
||||
CurrentScope.DeclareVariable(new Variable("this", structType, VariableKind.RValue));
|
||||
|
||||
var fields = node.Fields.Select(CheckStructField).ToList();
|
||||
var functions = node.Functions.Select(CheckStructFunc).ToList();
|
||||
var fields = node.Fields.Select(CheckStructField).ToList();
|
||||
var functions = node.Functions.Select(CheckStructFunc).ToList();
|
||||
|
||||
return new StructNode(CurrentScope.Module, node.Name, fields, functions);
|
||||
}
|
||||
return new StructNode(CurrentScope.Module, node.Name, fields, functions);
|
||||
}
|
||||
|
||||
private StructFuncNode CheckStructFunc(StructFuncSyntax function)
|
||||
@@ -157,42 +192,44 @@ public sealed class TypeChecker
|
||||
|
||||
private FuncNode CheckFuncDefinition(FuncSyntax node)
|
||||
{
|
||||
using (BeginRootScope(_syntaxTree.Metadata.ModuleName))
|
||||
foreach (var parameter in node.Signature.Parameters)
|
||||
{
|
||||
foreach (var parameter in node.Signature.Parameters)
|
||||
{
|
||||
CurrentScope.DeclareVariable(new Variable(parameter.Name, ResolveType(parameter.Type), VariableKind.RValue));
|
||||
}
|
||||
|
||||
var signature = CheckFuncSignature(node.Signature);
|
||||
|
||||
BlockNode? body = null;
|
||||
if (node.Body != null)
|
||||
{
|
||||
_funcReturnTypes.Push(signature.ReturnType);
|
||||
|
||||
body = CheckBlock(node.Body);
|
||||
|
||||
if (!AlwaysReturns(body))
|
||||
{
|
||||
if (signature.ReturnType is NubVoidType)
|
||||
{
|
||||
body.Statements.Add(new ReturnNode(null));
|
||||
}
|
||||
else
|
||||
{
|
||||
Diagnostics.Add(Diagnostic
|
||||
.Error("Not all code paths return a value")
|
||||
.At(node.Body)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
_funcReturnTypes.Pop();
|
||||
}
|
||||
|
||||
return new FuncNode(CurrentScope.Module, node.Name, node.ExternSymbol, signature, body);
|
||||
CurrentScope.DeclareVariable(new Variable(parameter.Name, ResolveType(parameter.Type), VariableKind.RValue));
|
||||
}
|
||||
|
||||
var signature = CheckFuncSignature(node.Signature);
|
||||
|
||||
BlockNode? body = null;
|
||||
if (node.Body != null)
|
||||
{
|
||||
_funcReturnTypes.Push(signature.ReturnType);
|
||||
|
||||
body = CheckBlock(node.Body);
|
||||
|
||||
if (!AlwaysReturns(body))
|
||||
{
|
||||
if (signature.ReturnType is NubVoidType)
|
||||
{
|
||||
body.Statements.Add(new ReturnNode(null));
|
||||
}
|
||||
else
|
||||
{
|
||||
Diagnostics.Add(Diagnostic
|
||||
.Error("Not all code paths return a value")
|
||||
.At(node.Body)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
_funcReturnTypes.Pop();
|
||||
}
|
||||
|
||||
return new FuncNode(CurrentScope.Module, node.Name, node.ExternSymbol, signature, body);
|
||||
}
|
||||
|
||||
private GlobalVariableNode CheckGlobalVariable(GlobalVariableSyntax node)
|
||||
{
|
||||
return new GlobalVariableNode(CurrentScope.Module, node.Name, CheckExpression(node.Value));
|
||||
}
|
||||
|
||||
private AssignmentNode CheckAssignment(AssignmentSyntax statement)
|
||||
@@ -701,6 +738,19 @@ public sealed class TypeChecker
|
||||
|
||||
var includePrivate = expression.Module == CurrentScope.Module;
|
||||
|
||||
var globalVariable = module.GlobalVariables(includePrivate).FirstOrDefault(x => x.Name == expression.Name);
|
||||
if (globalVariable != null)
|
||||
{
|
||||
// todo(nub31): This should be done in the global scope
|
||||
NubType? type = null;
|
||||
if (globalVariable.ExplicitType != null)
|
||||
{
|
||||
type = ResolveType(globalVariable.ExplicitType);
|
||||
}
|
||||
|
||||
return CheckExpression(globalVariable.Value, type);
|
||||
}
|
||||
|
||||
// First, look for the exported function in the specified module
|
||||
var function = module.Functions(includePrivate).FirstOrDefault(x => x.Name == expression.Name);
|
||||
if (function != null)
|
||||
@@ -733,8 +783,8 @@ public sealed class TypeChecker
|
||||
{
|
||||
return floatType.Width switch
|
||||
{
|
||||
32 => new Float32LiteralNode(floatType, float.Parse(expression.Value)),
|
||||
64 => new Float64LiteralNode(floatType, double.Parse(expression.Value)),
|
||||
32 => new Float32LiteralNode(floatType, float.Parse(expression.Value, CultureInfo.InvariantCulture)),
|
||||
64 => new Float64LiteralNode(floatType, double.Parse(expression.Value, CultureInfo.InvariantCulture)),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
@@ -749,6 +799,30 @@ public sealed class TypeChecker
|
||||
? new Float32LiteralNode(type, float.Parse(expression.Value, CultureInfo.InvariantCulture))
|
||||
: new Float64LiteralNode(type, double.Parse(expression.Value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
case LiteralKind.Hex:
|
||||
{
|
||||
if (expectedType is NubIntType intType)
|
||||
{
|
||||
return intType.Signed
|
||||
? new IntLiteralNode(intType, Convert.ToInt64(expression.Value, 16))
|
||||
: new UIntLiteralNode(intType, Convert.ToUInt64(expression.Value, 16));
|
||||
}
|
||||
|
||||
var type = new NubIntType(true, 64);
|
||||
return new IntLiteralNode(type, Convert.ToInt64(expression.Value, 16));
|
||||
}
|
||||
case LiteralKind.Binary:
|
||||
{
|
||||
if (expectedType is NubIntType intType)
|
||||
{
|
||||
return intType.Signed
|
||||
? new IntLiteralNode(intType, Convert.ToInt64(expression.Value[2..], 2))
|
||||
: new UIntLiteralNode(intType, Convert.ToUInt64(expression.Value[2..], 2));
|
||||
}
|
||||
|
||||
var type = new NubIntType(true, 64);
|
||||
return new IntLiteralNode(type, Convert.ToInt64(expression.Value.Substring(2), 2));
|
||||
}
|
||||
case LiteralKind.String:
|
||||
{
|
||||
return expectedType switch
|
||||
|
||||
Reference in New Issue
Block a user