Perf improvements in tokenizer

This commit is contained in:
nub31
2025-09-29 14:11:48 +02:00
parent b5799633bf
commit c74394a849
10 changed files with 219 additions and 274 deletions

View File

@@ -1,9 +1,7 @@
using NubLang.Code;
namespace NubLang.CLI; namespace NubLang.CLI;
public class Options public class Options
{ {
public string? OutputPath { get; set; } public string? OutputPath { get; set; }
public List<SourceFile> Files { get; } = []; public List<string> Files { get; } = [];
} }

View File

@@ -1,6 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using NubLang.CLI; using NubLang.CLI;
using NubLang.Code;
using NubLang.Diagnostics; using NubLang.Diagnostics;
using NubLang.Generation.QBE; using NubLang.Generation.QBE;
using NubLang.Modules; using NubLang.Modules;
@@ -32,7 +31,7 @@ for (var i = 0; i < args.Length; i++)
} }
default: default:
{ {
options.Files.Add(new SourceFile(arg)); options.Files.Add(arg);
break; break;
} }
} }
@@ -43,7 +42,7 @@ sw.Restart();
foreach (var file in options.Files) foreach (var file in options.Files)
{ {
if (!File.Exists(file.Path)) if (!File.Exists(file))
{ {
Console.Error.WriteLine($"File '{file}' does not exist"); Console.Error.WriteLine($"File '{file}' does not exist");
return 1; return 1;
@@ -58,18 +57,18 @@ var diagnostics = new List<Diagnostic>();
var syntaxTrees = new List<SyntaxTree>(); var syntaxTrees = new List<SyntaxTree>();
foreach (var file in options.Files) foreach (var file in options.Files)
{ {
var tokenizer = new Tokenizer(file); var tokenizer = new Tokenizer(file, File.ReadAllText(file));
var tokens = tokenizer.Tokenize().ToList(); tokenizer.Tokenize();
diagnostics.AddRange(tokenizer.GetDiagnostics()); diagnostics.AddRange(tokenizer.Diagnostics);
Console.WriteLine($"Tokenize: {Path.GetFileName(file.Path)}: {sw.ElapsedMilliseconds}ms"); Console.WriteLine($" Tokenize: {Path.GetFileName(file)}: {sw.ElapsedMilliseconds}ms");
sw.Restart(); sw.Restart();
var parser = new Parser(); var parser = new Parser();
var syntaxTree = parser.Parse(tokens); var syntaxTree = parser.Parse(tokenizer.Tokens);
diagnostics.AddRange(parser.GetDiagnostics()); diagnostics.AddRange(parser.Diagnostics);
Console.WriteLine($"Parse: {Path.GetFileName(file.Path)}: {sw.ElapsedMilliseconds}ms"); Console.WriteLine($" Parse: {Path.GetFileName(file)}: {sw.ElapsedMilliseconds}ms");
sw.Restart(); sw.Restart();
syntaxTrees.Add(syntaxTree); syntaxTrees.Add(syntaxTree);
@@ -91,7 +90,7 @@ foreach (var syntaxTree in syntaxTrees)
var typeChecker = new TypeChecker(syntaxTree, moduleRepository); var typeChecker = new TypeChecker(syntaxTree, moduleRepository);
typeChecker.Check(); typeChecker.Check();
Console.WriteLine($"Type check {syntaxTree.Metadata.ModuleName}: {sw.ElapsedMilliseconds}ms"); Console.WriteLine($" Type check {syntaxTree.Metadata.ModuleName}: {sw.ElapsedMilliseconds}ms");
sw.Restart(); sw.Restart();
definitions.AddRange(typeChecker.Definitions); definitions.AddRange(typeChecker.Definitions);

View File

@@ -1,41 +0,0 @@
namespace NubLang.Code;
public class SourceFile
{
private string? _content;
public SourceFile(string path)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
}
public string Path { get; }
public string GetText() => _content ??= File.ReadAllText(Path);
public override string ToString() => Path;
public override bool Equals(object? obj)
{
return obj is SourceFile other && other.Path == Path;
}
public override int GetHashCode()
{
return HashCode.Combine(typeof(SourceFile), Path);
}
public static bool operator ==(SourceFile? left, SourceFile? right) => Equals(left, right);
public static bool operator !=(SourceFile? left, SourceFile? right) => !Equals(left, right);
}
public class SourceFileSpan
{
public SourceFileSpan(SourceFile sourceFile, SourceSpan span)
{
SourceFile = sourceFile;
Span = span;
}
public SourceFile SourceFile { get; }
public SourceSpan Span { get; }
}

View File

@@ -2,8 +2,6 @@ namespace NubLang.Code;
public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation> public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
{ {
public static SourceLocation Zero => new(0, 0);
public SourceLocation(int line, int column) public SourceLocation(int line, int column)
{ {
Line = line; Line = line;

View File

@@ -2,34 +2,33 @@ namespace NubLang.Code;
public readonly struct SourceSpan : IEquatable<SourceSpan>, IComparable<SourceSpan> public readonly struct SourceSpan : IEquatable<SourceSpan>, IComparable<SourceSpan>
{ {
public static SourceSpan Zero => new(SourceLocation.Zero, SourceLocation.Zero);
public static SourceSpan Merge(params IEnumerable<SourceSpan> spans) public static SourceSpan Merge(params IEnumerable<SourceSpan> spans)
{ {
var spanArray = spans as SourceSpan[] ?? spans.ToArray(); var spanArray = spans as SourceSpan[] ?? spans.ToArray();
if (spanArray.Length == 0) if (spanArray.Length == 0)
{ {
return Zero; return new SourceSpan(string.Empty, new SourceLocation(0, 0), new SourceLocation(0, 0));
} }
var minStart = spanArray.Min(s => s.Start); var minStart = spanArray.Min(s => s.Start);
var maxEnd = spanArray.Max(s => s.End); var maxEnd = spanArray.Max(s => s.End);
return new SourceSpan(minStart, maxEnd); return new SourceSpan(spanArray[0].FilePath, minStart, maxEnd);
} }
public SourceSpan(SourceLocation start, SourceLocation end) public SourceSpan(string filePath, SourceLocation start, SourceLocation end)
{ {
if (start > end) if (start > end)
{ {
throw new ArgumentException("Start location cannot be after end location"); throw new ArgumentException("Start location cannot be after end location");
} }
FilePath = filePath;
Start = start; Start = start;
End = end; End = end;
} }
public string FilePath { get; }
public SourceLocation Start { get; } public SourceLocation Start { get; }
public SourceLocation End { get; } public SourceLocation End { get; }
@@ -37,15 +36,15 @@ public readonly struct SourceSpan : IEquatable<SourceSpan>, IComparable<SourceSp
{ {
if (Start == End) if (Start == End)
{ {
return $"{Start}"; return $"{FilePath}:{Start}";
} }
if (Start.Line == End.Line) if (Start.Line == End.Line)
{ {
return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}"; return Start.Column == End.Column ? $"{FilePath}:{Start}" : $"{FilePath}:{Start.Line}:{Start.Column}-{End.Column}";
} }
return $"{Start}-{End}"; return $"{FilePath}:{Start}-{End}";
} }
public bool Equals(SourceSpan other) => Start == other.Start && End == other.End; public bool Equals(SourceSpan other) => Start == other.Start && End == other.End;

View File

@@ -11,7 +11,7 @@ public class Diagnostic
{ {
private readonly DiagnosticSeverity _severity; private readonly DiagnosticSeverity _severity;
private readonly string _message; private readonly string _message;
private SourceFileSpan? _fileSpan; private SourceSpan? _span;
private string? _help; private string? _help;
public DiagnosticBuilder(DiagnosticSeverity severity, string message) public DiagnosticBuilder(DiagnosticSeverity severity, string message)
@@ -24,12 +24,7 @@ public class Diagnostic
{ {
if (node != null) if (node != null)
{ {
var first = node.Tokens.FirstOrDefault(); _span = SourceSpan.Merge(node.Tokens.Select(x => x.Span));
if (first?.FileSpan != null)
{
var span = SourceSpan.Merge(node.Tokens.Select(x => x.FileSpan.Span));
At(new SourceFileSpan(first.FileSpan.SourceFile, span));
}
} }
return this; return this;
@@ -39,29 +34,35 @@ public class Diagnostic
{ {
if (token != null) if (token != null)
{ {
At(token.FileSpan); At(token.Span);
} }
return this; return this;
} }
public DiagnosticBuilder At(SourceFileSpan? fileSpan) public DiagnosticBuilder At(SourceSpan? span)
{ {
if (fileSpan != null) if (span != null)
{ {
_fileSpan = fileSpan; _span = span;
} }
return this; return this;
} }
public DiagnosticBuilder At(string filePath, int line, int column)
{
_span = new SourceSpan(filePath, new SourceLocation(line, column), new SourceLocation(line, column));
return this;
}
public DiagnosticBuilder WithHelp(string help) public DiagnosticBuilder WithHelp(string help)
{ {
_help = help; _help = help;
return this; return this;
} }
public Diagnostic Build() => new(_severity, _message, _help, _fileSpan); public Diagnostic Build() => new(_severity, _message, _help, _span);
} }
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message); public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
@@ -71,14 +72,14 @@ public class Diagnostic
public DiagnosticSeverity Severity { get; } public DiagnosticSeverity Severity { get; }
public string Message { get; } public string Message { get; }
public string? Help { get; } public string? Help { get; }
public SourceFileSpan? FileSpan { get; } public SourceSpan? Span { get; }
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceFileSpan? fileSpan) private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceSpan? span)
{ {
Severity = severity; Severity = severity;
Message = message; Message = message;
Help = help; Help = help;
FileSpan = fileSpan; Span = span;
} }
public string FormatANSI() public string FormatANSI()
@@ -93,23 +94,23 @@ public class Diagnostic
_ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White) _ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
}); });
if (FileSpan != null) if (Span.HasValue)
{ {
sb.Append(ConsoleColors.Colorize($" at {FileSpan.SourceFile.Path}:{FileSpan.Span}", ConsoleColors.Faint)); sb.Append(ConsoleColors.Colorize($" at {Span.Value}", ConsoleColors.Faint));
} }
sb.Append(": "); sb.Append(": ");
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite)); sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
if (FileSpan != null) if (Span.HasValue)
{ {
sb.AppendLine(); sb.AppendLine();
var text = FileSpan.SourceFile.GetText(); var text = File.ReadAllText(Span.Value.FilePath);
var lines = text.Split('\n'); var lines = text.Split('\n');
var startLine = FileSpan.Span.Start.Line; var startLine = Span.Value.Start.Line;
var endLine = FileSpan.Span.End.Line; var endLine = Span.Value.End.Line;
const int CONTEXT_LINES = 3; const int CONTEXT_LINES = 3;
@@ -126,8 +127,8 @@ public class Diagnostic
sb.Append('╮'); sb.Append('╮');
sb.AppendLine(); sb.AppendLine();
var tokenizer = new Tokenizer(FileSpan.SourceFile); var tokenizer = new Tokenizer(Span.Value.FilePath, text);
var tokens = tokenizer.Tokenize().ToList(); tokenizer.Tokenize();
for (var i = contextStartLine; i <= contextEndLine; i++) for (var i = contextStartLine; i <= contextEndLine; i++)
{ {
@@ -136,7 +137,7 @@ public class Diagnostic
sb.Append("│ "); sb.Append("│ ");
sb.Append(i.ToString().PadRight(numberPadding)); sb.Append(i.ToString().PadRight(numberPadding));
sb.Append(" │ "); sb.Append(" │ ");
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokens)); sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokenizer.Tokens));
sb.Append(" │"); sb.Append(" │");
sb.AppendLine(); sb.AppendLine();
@@ -147,12 +148,12 @@ public class Diagnostic
if (i == startLine) if (i == startLine)
{ {
markerStartColumn = FileSpan.Span.Start.Column; markerStartColumn = Span.Value.Start.Column;
} }
if (i == endLine) if (i == endLine)
{ {
markerEndColumn = FileSpan.Span.End.Column; markerEndColumn = Span.Value.End.Column;
} }
var markerLength = markerEndColumn - markerStartColumn; var markerLength = markerEndColumn - markerStartColumn;
@@ -197,8 +198,8 @@ public class Diagnostic
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
var lineTokens = tokens var lineTokens = tokens
.Where(t => t.FileSpan.Span.Start.Line == lineNumber) .Where(t => t.Span.Start.Line == lineNumber)
.OrderBy(t => t.FileSpan.Span.Start.Column) .OrderBy(t => t.Span.Start.Column)
.ToList(); .ToList();
if (lineTokens.Count == 0) if (lineTokens.Count == 0)
@@ -210,8 +211,8 @@ public class Diagnostic
foreach (var token in lineTokens) foreach (var token in lineTokens)
{ {
var tokenStart = token.FileSpan.Span.Start.Column; var tokenStart = token.Span.Start.Column;
var tokenEnd = token.FileSpan.Span.End.Column; var tokenEnd = token.Span.End.Column;
if (tokenStart > currentColumn) if (tokenStart > currentColumn)
{ {

View File

@@ -7,7 +7,6 @@ namespace NubLang.Parsing;
public sealed class Parser public sealed class Parser
{ {
private readonly List<Diagnostic> _diagnostics = [];
private readonly HashSet<string> _templateArguments = []; private readonly HashSet<string> _templateArguments = [];
private List<Token> _tokens = []; private List<Token> _tokens = [];
private int _tokenIndex; private int _tokenIndex;
@@ -16,14 +15,11 @@ public sealed class Parser
private Token? CurrentToken => _tokenIndex < _tokens.Count ? _tokens[_tokenIndex] : null; private Token? CurrentToken => _tokenIndex < _tokens.Count ? _tokens[_tokenIndex] : null;
private bool HasToken => CurrentToken != null; private bool HasToken => CurrentToken != null;
public List<Diagnostic> GetDiagnostics() public List<Diagnostic> Diagnostics { get; } = [];
{
return _diagnostics;
}
public SyntaxTree Parse(List<Token> tokens) public SyntaxTree Parse(List<Token> tokens)
{ {
_diagnostics.Clear(); Diagnostics.Clear();
_tokens = tokens; _tokens = tokens;
_tokenIndex = 0; _tokenIndex = 0;
_moduleName = string.Empty; _moduleName = string.Empty;
@@ -51,7 +47,7 @@ public sealed class Parser
} }
catch (ParseException e) catch (ParseException e)
{ {
_diagnostics.Add(e.Diagnostic); Diagnostics.Add(e.Diagnostic);
while (HasToken) while (HasToken)
{ {
if (CurrentToken is SymbolToken { Symbol: Symbol.Module or Symbol.Import }) if (CurrentToken is SymbolToken { Symbol: Symbol.Module or Symbol.Import })
@@ -102,7 +98,7 @@ public sealed class Parser
} }
catch (ParseException e) catch (ParseException e)
{ {
_diagnostics.Add(e.Diagnostic); Diagnostics.Add(e.Diagnostic);
while (HasToken) while (HasToken)
{ {
if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct }) if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct })
@@ -692,7 +688,7 @@ public sealed class Parser
} }
catch (ParseException ex) catch (ParseException ex)
{ {
_diagnostics.Add(ex.Diagnostic); Diagnostics.Add(ex.Diagnostic);
if (HasToken) if (HasToken)
{ {
Next(); Next();

View File

@@ -2,22 +2,6 @@
namespace NubLang.Tokenization; namespace NubLang.Tokenization;
public abstract class Token(SourceFileSpan fileSpan)
{
public SourceFileSpan FileSpan { get; } = fileSpan;
}
public class IdentifierToken(SourceFileSpan fileSpan, string value) : Token(fileSpan)
{
public string Value { get; } = value;
}
public class LiteralToken(SourceFileSpan fileSpan, LiteralKind kind, string value) : Token(fileSpan)
{
public LiteralKind Kind { get; } = kind;
public string Value { get; } = value;
}
public enum LiteralKind public enum LiteralKind
{ {
Integer, Integer,
@@ -26,11 +10,6 @@ public enum LiteralKind
Bool Bool
} }
public class SymbolToken(SourceFileSpan fileSpan, Symbol symbol) : Token(fileSpan)
{
public Symbol Symbol { get; } = symbol;
}
public enum Symbol public enum Symbol
{ {
Func, Func,
@@ -84,3 +63,11 @@ public enum Symbol
At, At,
Enum, Enum,
} }
public abstract record Token(string FileName, SourceSpan Span);
public record IdentifierToken(string FileName, SourceSpan Span, string Value) : Token(FileName, Span);
public record LiteralToken(string FileName, SourceSpan Span, LiteralKind Kind, string Value) : Token(FileName, Span);
public record SymbolToken(string FileName, SourceSpan Span, Symbol Symbol) : Token(FileName, Span);

View File

@@ -68,171 +68,196 @@ public sealed class Tokenizer
.Select(kvp => (kvp.Key, kvp.Value)) .Select(kvp => (kvp.Key, kvp.Value))
.ToArray(); .ToArray();
private readonly SourceFile _sourceFile; private readonly string _fileName;
private readonly List<Diagnostic> _diagnostics = []; private readonly string _content;
private int _index; private int _index = 0;
private int _line = 1;
private int _column = 1;
public Tokenizer(SourceFile sourceFile) public Tokenizer(string fileName, string content)
{ {
_sourceFile = sourceFile; _fileName = fileName;
_content = content;
} }
public List<Diagnostic> GetDiagnostics() => _diagnostics; public List<Diagnostic> Diagnostics { get; } = [];
public List<Token> Tokens { get; } = [];
public IEnumerable<Token> Tokenize() public void Tokenize()
{ {
Diagnostics.Clear();
Tokens.Clear();
_index = 0; _index = 0;
_line = 1;
_column = 1;
while (Peek() != null) while (Peek().HasValue)
{ {
var current = Peek()!.Value; try
if (char.IsWhiteSpace(current))
{ {
Next(); // Skip whitespace and increment line counter if newline
continue; var current = Peek()!.Value;
} if (char.IsWhiteSpace(current))
if (current == '/' && Peek(1) == '/')
{
while (Peek().HasValue && Peek() != '\n')
{ {
if (current is '\n')
{
_line += 1;
_column = 1;
}
Next(); Next();
}
continue;
}
var tokenStartIndex = _index;
if (char.IsLetter(current) || current == '_')
{
var buffer = string.Empty;
while (Peek() != null && (char.IsLetterOrDigit(Peek()!.Value) || Peek() == '_'))
{
buffer += Peek();
Next();
}
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
{
yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), keywordSymbol);
continue; continue;
} }
if (buffer is "true" or "false") // Skip single line comments but keep newline so next iteration increments the line counter
if (current == '/' && Peek(1) == '/')
{ {
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.Bool, buffer); while (Peek() is not '\n')
{
Next();
}
continue; continue;
} }
yield return new IdentifierToken(GetSourceFileSpan(tokenStartIndex), buffer); Tokens.Add(ParseToken(current, _line, _column));
continue;
} }
catch (TokenizerException e)
if (char.IsDigit(current))
{
var isFloat = false;
var buffer = string.Empty;
while (Peek() != null)
{
var next = Peek()!.Value;
if (next == '.')
{
if (isFloat)
{
throw new Exception("More than one period found in float literal");
}
isFloat = true;
buffer += next;
Next();
}
else if (char.IsDigit(next))
{
buffer += next;
Next();
}
else
{
break;
}
}
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
continue;
}
if (current == '"')
{ {
Diagnostics.Add(e.Diagnostic);
Next(); Next();
var buffer = string.Empty; }
}
}
while (true) private Token ParseToken(char current, int lineStart, int columnStart)
{
if (char.IsLetter(current) || current == '_')
{
var buffer = string.Empty;
while (Peek() != null && (char.IsLetterOrDigit(Peek()!.Value) || Peek() == '_'))
{
buffer += Peek();
Next();
}
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
{
return new SymbolToken(_fileName, CreateSpan(lineStart, columnStart), keywordSymbol);
}
if (buffer is "true" or "false")
{
return new LiteralToken(_fileName, CreateSpan(lineStart, columnStart), LiteralKind.Bool, buffer);
}
return new IdentifierToken(_fileName, CreateSpan(lineStart, columnStart), buffer);
}
if (char.IsDigit(current))
{
var isFloat = false;
var buffer = string.Empty;
while (Peek() != null)
{
var next = Peek()!.Value;
if (next == '.')
{ {
if (Peek() == null) if (isFloat)
{ {
throw new Exception("Unclosed string literal"); throw new TokenizerException(Diagnostic
} .Error("More than one period found in float literal")
.At(_fileName, _line, _column)
var next = Peek()!.Value; .Build());
if (next == '"')
{
Next();
break;
} }
isFloat = true;
buffer += next; buffer += next;
Next(); Next();
} }
else if (char.IsDigit(next))
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.String, buffer);
continue;
}
var foundMatch = false;
foreach (var (pattern, symbol) in OrderedSymbols)
{
for (var i = 0; i < pattern.Length; i++)
{ {
var c = Peek(i); buffer += next;
if (!c.HasValue || c.Value != pattern[i]) break; Next();
if (i == pattern.Length - 1)
{
for (var j = 0; j <= i; j++)
{
Next();
}
yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), symbol);
foundMatch = true;
break;
}
} }
else
if (foundMatch)
{ {
break; break;
} }
} }
if (foundMatch) return new LiteralToken(_fileName, CreateSpan(lineStart, columnStart), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
}
if (current == '"')
{
Next();
var buffer = string.Empty;
while (true)
{ {
continue; var next = Peek();
if (!next.HasValue)
{
throw new TokenizerException(Diagnostic
.Error("Unclosed string literal")
.At(_fileName, _line, _column)
.Build());
}
if (next is '\n')
{
_line += 1;
break;
}
if (next is '"')
{
Next();
break;
}
buffer += next;
Next();
} }
_diagnostics.Add(Diagnostic.Error($"Unknown token '{current}'").At(GetSourceFileSpan(tokenStartIndex)).Build()); return new LiteralToken(_fileName, CreateSpan(lineStart, columnStart), LiteralKind.String, buffer);
Next();
} }
foreach (var (pattern, symbol) in OrderedSymbols)
{
for (var i = 0; i < pattern.Length; i++)
{
var c = Peek(i);
if (!c.HasValue || c.Value != pattern[i]) break;
if (i == pattern.Length - 1)
{
for (var j = 0; j <= i; j++)
{
Next();
}
return new SymbolToken(_fileName, CreateSpan(lineStart, columnStart), symbol);
}
}
}
throw new TokenizerException(Diagnostic.Error($"Unknown token '{current}'").Build());
}
private SourceSpan CreateSpan(int lineStart, int columnStart)
{
return new SourceSpan(_fileName, new SourceLocation(lineStart, columnStart), new SourceLocation(_line, _column));
} }
private char? Peek(int offset = 0) private char? Peek(int offset = 0)
{ {
if (_index + offset < _sourceFile.GetText().Length) if (_index + offset < _content.Length)
{ {
return _sourceFile.GetText()[_index + offset]; return _content[_index + offset];
} }
return null; return null;
@@ -240,34 +265,17 @@ public sealed class Tokenizer
private void Next() private void Next()
{ {
_index++; _index += 1;
} _column += 1;
}
private SourceFileSpan GetSourceFileSpan(int tokenStartIndex) }
{
var start = CalculateSourceLocation(tokenStartIndex); public class TokenizerException : Exception
var end = CalculateSourceLocation(_index); {
return new SourceFileSpan(_sourceFile, new SourceSpan(start, end)); public Diagnostic Diagnostic { get; }
}
public TokenizerException(Diagnostic diagnostic) : base(diagnostic.Message)
private SourceLocation CalculateSourceLocation(int index) {
{ Diagnostic = diagnostic;
var line = 1;
var column = 1;
for (var i = 0; i < index && i < _sourceFile.GetText().Length; i++)
{
if (_sourceFile.GetText()[i] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
}
return new SourceLocation(line, column);
} }
} }

View File

@@ -6,7 +6,7 @@ out: .build/out.o
.build/out.o: $(NUBC) src/main.nub src/raylib.nub .build/out.o: $(NUBC) src/main.nub src/raylib.nub
$(NUBC) src/main.nub src/raylib.nub $(NUBC) src/main.nub src/raylib.nub
# .PHONY: $(NUBC) .PHONY: $(NUBC)
$(NUBC): $(NUBC):
dotnet build ../compiler/NubLang.CLI/NubLang.CLI.csproj dotnet build ../compiler/NubLang.CLI/NubLang.CLI.csproj