diff --git a/example/program.nub b/example/program.nub index 6a1e761..2da8f81 100644 --- a/example/program.nub +++ b/example/program.nub @@ -9,7 +9,7 @@ struct Human { export func main(args: []^string) { let i: i64 - c:printf("%d\n", args.count) + c::printf("%d\n", args.count) while i < args.count { c::printf("%s\n", args[i]) diff --git a/src/lang/Nub.Lang.CLI/Program.cs b/src/lang/Nub.Lang.CLI/Program.cs index 8d1efda..e62add6 100644 --- a/src/lang/Nub.Lang.CLI/Program.cs +++ b/src/lang/Nub.Lang.CLI/Program.cs @@ -30,7 +30,7 @@ foreach (var file in Directory.EnumerateFiles(srcDir, "*.nub", SearchOption.AllD { var content = File.ReadAllText(file); - var tokenizeResult = lexer.Tokenize(new SourceText(content)); + var tokenizeResult = lexer.Tokenize(new SourceText(file, content)); tokenizeResult.PrintAllDiagnostics(); error = error || tokenizeResult.HasErrors; diff --git a/src/lang/Nub.Lang/Frontend/Diagnostics/ConsoleColors.cs b/src/lang/Nub.Lang/Frontend/Diagnostics/ConsoleColors.cs index 555af8c..fc8264c 100644 --- a/src/lang/Nub.Lang/Frontend/Diagnostics/ConsoleColors.cs +++ b/src/lang/Nub.Lang/Frontend/Diagnostics/ConsoleColors.cs @@ -1,17 +1,54 @@ +using System.Text; +using Nub.Lang.Frontend.Lexing; +using Nub.Lang.Frontend.Typing; + namespace Nub.Lang.Frontend.Diagnostics; public static class ConsoleColors { public const string Reset = "\e[0m"; public const string Bold = "\e[1m"; + public const string Faint = "\e[2m"; + public const string Italic = "\e[3m"; + public const string Underline = "\e[4m"; + public const string SlowBlink = "\e[5m"; + public const string RapidBlink = "\e[6m"; + public const string SwapBgAndFg = "\e[7m"; + public const string Conceal = "\e[8m"; + public const string CrossedOut = "\e[9m"; + + public const string DefaultFont = "\e[10m"; + public const string AltFont1 = "\e[11m"; + public const string AltFont2 = "\e[12m"; + public const string AltFont3 = "\e[13m"; + public const string AltFont4 = "\e[14m"; + public const string AltFont5 = "\e[15m"; + public const string AltFont6 = "\e[16m"; + public const string AltFont7 = "\e[17m"; + public const string AltFont8 = "\e[18m"; + public const string AltFont9 = "\e[19m"; + public const string Black = "\e[30m"; public const string Red = "\e[31m"; + public const string Green = "\e[32m"; public const string Yellow = "\e[33m"; public const string Blue = "\e[34m"; + public const string Magenta = "\e[35m"; public const string Cyan = "\e[36m"; public const string White = "\e[37m"; + + public const string LightBlue = "\e[94m"; + + public const string BrightBlack = "\e[90m"; + public const string BrightRed = "\e[91m"; + public const string BrightGreen = "\e[92m"; + public const string BrightYellow = "\e[93m"; + public const string BrightBlue = "\e[94m"; + public const string BrightMagenta = "\e[95m"; + public const string BrightCyan = "\e[96m"; public const string BrightWhite = "\e[97m"; - public const string Gray = "\e[90m"; + + private static readonly Lexer Lexer = new(); private static bool IsColorSupported() { @@ -24,4 +61,130 @@ public static class ConsoleColors { return IsColorSupported() ? $"{color}{text}{Reset}" : text; } + + private static string GetTokenColor(Token token) + { + switch (token) + { + case DocumentationToken: + return Faint; + case IdentifierToken: + return White; + case LiteralToken literal: + if (literal.Type.Equals(NubPrimitiveType.String)) + { + return Green; + } + + if (literal.Type.Equals(NubPrimitiveType.I64) || literal.Type.Equals(NubPrimitiveType.F64)) + { + return LightBlue; + } + + if (literal.Type.Equals(NubPrimitiveType.Bool)) + { + return Blue; + } + + return White; + case ModifierToken: + return White; + case SymbolToken symbol: + switch (symbol.Symbol) + { + case Symbol.If: + case Symbol.Else: + case Symbol.While: + case Symbol.Break: + case Symbol.Continue: + case Symbol.Return: + return Magenta; + case Symbol.Func: + case Symbol.Struct: + case Symbol.Namespace: + case Symbol.Let: + case Symbol.Alloc: + return Blue; + case Symbol.Assign: + case Symbol.Bang: + case Symbol.Equal: + case Symbol.NotEqual: + case Symbol.LessThan: + case Symbol.LessThanOrEqual: + case Symbol.GreaterThan: + case Symbol.GreaterThanOrEqual: + case Symbol.Plus: + case Symbol.Minus: + case Symbol.Star: + case Symbol.ForwardSlash: + case Symbol.Caret: + case Symbol.Ampersand: + return White; + case Symbol.Semicolon: + case Symbol.Colon: + case Symbol.Comma: + case Symbol.Period: + case Symbol.DoubleColon: + return Faint; + case Symbol.OpenParen: + case Symbol.CloseParen: + case Symbol.OpenBrace: + case Symbol.CloseBrace: + case Symbol.OpenBracket: + case Symbol.CloseBracket: + return BrightYellow; + default: + return White; + } + default: + return White; + } + } + + public static string ColorizeSource(SourceText sourceText) + { + var tokens = Lexer.Tokenize(sourceText).Value; + var result = new StringBuilder(); + var lastCharIndex = 0; + + foreach (var token in tokens) + { + var tokenStartIndex = GetCharacterIndex(sourceText, token.Span.Start); + var tokenEndIndex = GetCharacterIndex(sourceText, token.Span.End); + + if (tokenStartIndex > lastCharIndex) + { + var between = sourceText.Content.Substring(lastCharIndex, tokenStartIndex - lastCharIndex); + result.Append(between); + } + + var tokenText = sourceText.Content.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex); + + result.Append(Colorize(tokenText, GetTokenColor(token))); + lastCharIndex = tokenEndIndex; + } + + if (lastCharIndex < sourceText.Content.Length) + { + var remaining = sourceText.Content[lastCharIndex..]; + result.Append(Colorize(remaining, Faint)); + } + + return result.ToString(); + } + + private static int GetCharacterIndex(SourceText sourceText, SourceLocation location) + { + var lines = sourceText.Content.Split('\n'); + var index = 0; + + for (var i = 0; i < location.Line - 1 && i < lines.Length; i++) + { + index += lines[i].Length + 1; + } + + index += location.Column - 1; + + return Math.Min(index, sourceText.Content.Length); + } } \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Diagnostics/Diagnostic.cs b/src/lang/Nub.Lang/Frontend/Diagnostics/Diagnostic.cs index be9587f..6d9455a 100644 --- a/src/lang/Nub.Lang/Frontend/Diagnostics/Diagnostic.cs +++ b/src/lang/Nub.Lang/Frontend/Diagnostics/Diagnostic.cs @@ -72,8 +72,8 @@ public class Diagnostic if (Span.HasValue) { - // var locationText = $" at {Span.Value.Path}:{Span}"; - // sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Gray)); + var locationText = $" at {Span.Value.Text.Name}:{Span}"; + sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Faint)); } sb.Append(": "); @@ -107,7 +107,7 @@ public class Diagnostic private static void AppendSourceContext(StringBuilder sb, SourceSpan span) { - var lines = span.Content.Text.Split('\n'); + var lines = span.Text.Content.Split('\n'); var startLine = span.Start.Line; var endLine = span.End.Line; @@ -125,13 +125,13 @@ public class Diagnostic for (var lineNum = contextStart; lineNum < startLine; lineNum++) { - var line = lines[lineNum - 1]; + var line = ConsoleColors.ColorizeSource(new SourceText(span.Text.Name, lines[lineNum - 1])); AppendContextLine(sb, lineNum, line, lineNumWidth); } for (var lineNum = startLine; lineNum <= endLine; lineNum++) { - var line = lines[lineNum - 1]; + var line = ConsoleColors.ColorizeSource(new SourceText(span.Text.Name, lines[lineNum - 1])); AppendContextLine(sb, lineNum, line, lineNumWidth); AppendErrorIndicators(sb, span, lineNum, line, lineNumWidth); } @@ -139,7 +139,7 @@ public class Diagnostic var contextEnd = Math.Min(lines.Length, endLine + contextLines); for (var lineNum = endLine + 1; lineNum <= contextEnd; lineNum++) { - var line = lines[lineNum - 1]; + var line = ConsoleColors.ColorizeSource(new SourceText(span.Text.Name, lines[lineNum - 1])); AppendContextLine(sb, lineNum, line, lineNumWidth); } } @@ -147,8 +147,8 @@ public class Diagnostic private static void AppendContextLine(StringBuilder sb, int lineNum, string line, int lineNumWidth) { var lineNumStr = lineNum.ToString().PadLeft(lineNumWidth); - sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Gray)); - sb.Append(ConsoleColors.Colorize(" | ", ConsoleColors.Gray)); + sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Faint)); + sb.Append(ConsoleColors.Colorize(" | ", ConsoleColors.Faint)); sb.AppendLine(line); } diff --git a/src/lang/Nub.Lang/Frontend/Lexing/DocumentationToken.cs b/src/lang/Nub.Lang/Frontend/Lexing/DocumentationToken.cs index 139494b..d276789 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/DocumentationToken.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/DocumentationToken.cs @@ -1,5 +1,3 @@ -using Nub.Lang.Frontend.Diagnostics; - namespace Nub.Lang.Frontend.Lexing; public class DocumentationToken(SourceSpan span, string documentation) : Token(span) diff --git a/src/lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs b/src/lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs index 62b4664..f0e60ad 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/IdentifierToken.cs @@ -1,6 +1,4 @@ -using Nub.Lang.Frontend.Diagnostics; - -namespace Nub.Lang.Frontend.Lexing; +namespace Nub.Lang.Frontend.Lexing; public class IdentifierToken(SourceSpan span, string value) : Token(span) { diff --git a/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs b/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs index ce3c76d..5771545 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs @@ -245,10 +245,10 @@ public class Lexer private SourceLocation CreateLocation(int index) { var line = 1; - var column = 0; + var column = 1; for (var i = 0; i < index; i++) { - if (_sourceText.Text[i] == '\n') + if (_sourceText.Content[i] == '\n') { column = 1; line += 1; @@ -269,9 +269,9 @@ public class Lexer private Optional Peek(int offset = 0) { - if (_index + offset < _sourceText.Text.Length) + if (_index + offset < _sourceText.Content.Length) { - return _sourceText.Text[_index + offset]; + return _sourceText.Content[_index + offset]; } return Optional.Empty(); diff --git a/src/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs b/src/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs index 07b236f..948a079 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/LiteralToken.cs @@ -1,5 +1,4 @@ -using Nub.Lang.Frontend.Diagnostics; -using Nub.Lang.Frontend.Typing; +using Nub.Lang.Frontend.Typing; namespace Nub.Lang.Frontend.Lexing; diff --git a/src/lang/Nub.Lang/Frontend/Lexing/ModifierToken.cs b/src/lang/Nub.Lang/Frontend/Lexing/ModifierToken.cs index cf9b890..d9d0a25 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/ModifierToken.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/ModifierToken.cs @@ -1,5 +1,3 @@ -using Nub.Lang.Frontend.Diagnostics; - namespace Nub.Lang.Frontend.Lexing; public class ModifierToken(SourceSpan span, Modifier modifier) : Token(span) diff --git a/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs b/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs index de3314e..baa5502 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs @@ -1,6 +1,4 @@ -using Nub.Lang.Frontend.Diagnostics; - -namespace Nub.Lang.Frontend.Lexing; +namespace Nub.Lang.Frontend.Lexing; public class SymbolToken(SourceSpan span, Symbol symbol) : Token(span) { diff --git a/src/lang/Nub.Lang/Frontend/Lexing/Token.cs b/src/lang/Nub.Lang/Frontend/Lexing/Token.cs index 05d903f..6dabe8c 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/Token.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/Token.cs @@ -1,6 +1,4 @@ -using Nub.Lang.Frontend.Diagnostics; - -namespace Nub.Lang.Frontend.Lexing; +namespace Nub.Lang.Frontend.Lexing; public abstract class Token(SourceSpan span) { diff --git a/src/lang/Nub.Lang/Source.cs b/src/lang/Nub.Lang/Source.cs index 2658c75..caf7c61 100644 --- a/src/lang/Nub.Lang/Source.cs +++ b/src/lang/Nub.Lang/Source.cs @@ -2,9 +2,9 @@ using System.Diagnostics.CodeAnalysis; namespace Nub.Lang; -public readonly struct SourceSpan(SourceText content, SourceLocation start, SourceLocation end) : IEquatable +public readonly struct SourceSpan(SourceText text, SourceLocation start, SourceLocation end) : IEquatable { - public SourceText Content { get; } = content; + public SourceText Text { get; } = text; public SourceLocation Start { get; } = start; public SourceLocation End { get; } = end; @@ -21,7 +21,7 @@ public readonly struct SourceSpan(SourceText content, SourceLocation start, Sour throw new ArgumentException("Cannot merge empty spans", nameof(spanEnumerable)); } - var files = spans.Select(s => s.Content).Distinct().ToArray(); + var files = spans.Select(s => s.Text).Distinct().ToArray(); if (files.Length > 1) { throw new ArgumentException("Cannot merge spans from multiple files", nameof(spanEnumerable)); @@ -55,12 +55,12 @@ public readonly struct SourceSpan(SourceText content, SourceLocation start, Sour public bool Equals(SourceSpan other) { - return Content.Equals(other.Content) && Start.Equals(other.Start) && End.Equals(other.End); + return Text.Equals(other.Text) && Start.Equals(other.Start) && End.Equals(other.End); } public override int GetHashCode() { - return HashCode.Combine(Content, Start, End); + return HashCode.Combine(Text, Start, End); } public static bool operator ==(SourceSpan left, SourceSpan right) @@ -74,13 +74,14 @@ public readonly struct SourceSpan(SourceText content, SourceLocation start, Sour } } -public readonly struct SourceText(string text) : IEquatable +public readonly struct SourceText(string name, string content) : IEquatable { - public string Text { get; } = text; + public string Name { get; } = name; + public string Content { get; } = content; public bool Equals(SourceText other) { - return Text == other.Text; + return Content == other.Content; } public override bool Equals([NotNullWhen(true)] object? obj) @@ -90,7 +91,7 @@ public readonly struct SourceText(string text) : IEquatable public override int GetHashCode() { - return Text.GetHashCode(); + return Content.GetHashCode(); } public static bool operator ==(SourceText left, SourceText right)