Skip to content

Commit

Permalink
Added CodeEditor and syntax highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
Walper committed Nov 22, 2023
1 parent c72b0b3 commit 71d236f
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 20 deletions.
2 changes: 0 additions & 2 deletions LanguageInterpreter/Interpreter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using LanguageInterpreter.Execution;
using LanguageParser;
using LanguageParser.Common;
using LanguageParser.Parser;
using LanguageParser.Visitors;
Expand Down
2 changes: 1 addition & 1 deletion LanguageParser/Lexer/Tokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace LanguageParser.Lexer;

internal sealed class Tokenizer
public sealed class Tokenizer
{
private readonly CharStream _charStream;
private readonly ImmutableArray<Token>.Builder _triviaBuilder = ImmutableArray.CreateBuilder<Token>();
Expand Down
5 changes: 5 additions & 0 deletions LanguageParser/Visitors/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using LanguageParser.Expressions;
using LanguageParser.Lexer;

namespace LanguageParser.Visitors;

public abstract class ExpressionVisitor
{
public virtual void Visit(ExpressionBase expression) => expression.Visit(this);

public virtual void VisitTrivia(Token trivia)
{
}

public virtual void VisitBinary(BinaryExpression expression)
{
}
Expand Down
1 change: 1 addition & 0 deletions SimpleExecutor/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@

<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
</Application.Styles>
</Application>
34 changes: 34 additions & 0 deletions SimpleExecutor/Models/ExpressionSyntaxColorizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;
using LanguageParser.Parser;
using SimpleExecutor.ViewModels;

namespace SimpleExecutor.Models;

public sealed class ExpressionSyntaxColorizer : DocumentColorizingTransformer
{
private readonly MainViewModel _viewModel;

public ExpressionSyntaxColorizer(MainViewModel viewModel)
{
_viewModel = viewModel;
}

protected override void ColorizeLine(DocumentLine line)
{
try
{
var result = ExpressionsParser.Parse(_viewModel.Code);

if (result.IsError)
return;

ExpressionsColorizer.Colorize(ChangeLinePart, line, result.Value);
}
catch (Exception e)

Check warning on line 29 in SimpleExecutor/Models/ExpressionSyntaxColorizer.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'e' is declared but never used

Check warning on line 29 in SimpleExecutor/Models/ExpressionSyntaxColorizer.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'e' is declared but never used
{
// ignored
}
}
}
164 changes: 164 additions & 0 deletions SimpleExecutor/Models/ExpressionsColorizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using Avalonia.Media;
using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;
using LanguageParser.Common;
using LanguageParser.Expressions;
using LanguageParser.Lexer;
using LanguageParser.Visitors;
using static SimpleExecutor.Models.SyntaxColors;

namespace SimpleExecutor.Models;

public sealed class ExpressionsColorizer : ExpressionVisitor
{
private readonly Action<int, int, Action<VisualLineElement>> _changeLinePart;
private readonly DocumentLine _line;

private ExpressionsColorizer(Action<int, int, Action<VisualLineElement>> changeLinePart, DocumentLine line)
{
_changeLinePart = changeLinePart;
_line = line;
}

public static void Colorize(Action<int, int, Action<VisualLineElement>> changeLinePart, DocumentLine line,
ExpressionBase expression)
{
var colorizer = new ExpressionsColorizer(changeLinePart, line);
colorizer.Visit(expression);

foreach (var trivia in expression.TrailingTrivia)
colorizer.VisitTrivia(trivia);
}

public override void VisitTrivia(Token trivia)
{
if (trivia.IsComment)
SetForeground(trivia, CommentBrush);
}

public override void VisitBinary(BinaryExpression expression)
{
Visit(expression.Left);
SetForeground(expression.Operator.Token, OperatorBrush);
Visit(expression.Right);
}

public override void VisitConstant(ConstantExpression expression)
{
SetForeground(expression.Token, GetTokenBrush(expression.Token));
}

public override void VisitFor(ForExpression expression)
{
SetForeground(expression.ForToken, KeywordBrush);
if (expression.Initialization is not null)
Visit(expression.Initialization);

if (expression.Condition is not null)
Visit(expression.Condition);

if (expression.Step is not null)
Visit(expression.Step);

Visit(expression.Body);
}

public override void VisitIf(IfExpression expression)
{
SetForeground(expression.IfToken, KeywordBrush);
Visit(expression.Condition);

Visit(expression.ThenBranch);

if (expression.ElseToken is not null)
SetForeground(expression.ElseToken, KeywordBrush);

if (expression.ElseBranch is not null)
Visit(expression.ElseBranch);
}

public override void VisitInvocation(InvocationExpression expression)
{
if (expression.Function is ConstantExpression constant)
SetForeground(constant.Token, FunctionBrush);
else
Visit(expression.Function);

foreach (var argument in expression.Arguments)
Visit(argument);
}

public override void VisitParenthesized(ParenthesizedExpression expression)
{
Visit(expression.Expression);
}

public override void VisitRepeat(RepeatExpression expression)
{
SetForeground(expression.RepeatToken, KeywordBrush);

Visit(expression.Body);

SetForeground(expression.UntilToken, KeywordBrush);

Visit(expression.Condition);
}

public override void VisitScope(ScopeExpression expression)
{
foreach (var innerExpression in expression.InnerExpressions)
Visit(innerExpression);
}

public override void VisitVariable(VariableExpression expression)
{
SetForeground(expression.TypeToken, KeywordBrush);

SetForeground(expression.NameToken, GetTokenBrush(expression.NameToken));

if (expression.AssignmentExpression is not null)
Visit(expression.AssignmentExpression);
}

public override void VisitWhile(WhileExpression expression)
{
SetForeground(expression.WhileToken, KeywordBrush);

Visit(expression.Condition);
Visit(expression.Body);
}

private void WalkLeadingTrivia(Token token)
{
foreach (var trivia in token.LeadingTrivia)
VisitTrivia(trivia);
}

private void SetForeground(StringRange range, IBrush brush)
{
if (new StringRange(_line.Offset, _line.Length).Intersection(range) is { } intersection)
_changeLinePart.Invoke(intersection.Start, intersection.End,
e => e.TextRunProperties.SetForegroundBrush(brush));
}

private void SetForeground(Token token, IImmutableBrush brush)
{
WalkLeadingTrivia(token);
SetForeground(token.Range, brush);
}

private static IImmutableBrush GetTokenBrush(Token token)
{
if (token.IsNumber)
return NumberBrush;

if (token.IsKeyWord)
return KeywordBrush;

if (token.IsString)
return StringBrush;

return ForegroundBrush;
}
}
26 changes: 26 additions & 0 deletions SimpleExecutor/Models/SyntaxColors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Avalonia.Media;

namespace SimpleExecutor.Models;

public static class SyntaxColors
{
public static readonly IImmutableBrush FunctionBrush =
new SolidColorBrush(Color.FromRgb(21, 139, 134)).ToImmutable();

public static readonly IImmutableBrush KeywordBrush = new SolidColorBrush(Color.FromRgb(80, 125, 233)).ToImmutable();

public static readonly IImmutableBrush StringBrush =
new SolidColorBrush(Color.FromRgb(196, 158, 107)).ToImmutable();

public static readonly IImmutableBrush NumberBrush =
new SolidColorBrush(Color.FromRgb(237, 148, 192)).ToImmutable();

public static readonly IImmutableBrush OperatorBrush =
new SolidColorBrush(Color.FromRgb(50,50,50)).ToImmutable();

public static readonly IImmutableBrush CommentBrush =
new SolidColorBrush(Color.FromRgb(81, 115, 66)).ToImmutable();

public static readonly IImmutableBrush ForegroundBrush =
new SolidColorBrush(Color.FromRgb(50,50,50)).ToImmutable();
}
93 changes: 93 additions & 0 deletions SimpleExecutor/Models/TokensColorizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Linq;
using Avalonia.Media;
using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;
using LanguageParser.Common;
using LanguageParser.Interfaces;
using LanguageParser.Lexer;
using static SimpleExecutor.Models.SyntaxColors;
using Tokenizer = LanguageParser.Lexer.Tokenizer;

namespace SimpleExecutor.Models;

public sealed class TokensColorizer
{
private readonly Action<int, int, Action<VisualLineElement>> _changeLinePart;
private readonly DocumentLine _line;
private readonly IStream<Token> _tokenStream;

public TokensColorizer(IStream<Token> tokenStream, Action<int, int, Action<VisualLineElement>> changeLinePart,
DocumentLine line)
{
_tokenStream = tokenStream;
_changeLinePart = changeLinePart;
_line = line;
}

public static void Colorize(string code, Action<int, int, Action<VisualLineElement>> changeLinePart, DocumentLine line)
{
var tokens = Tokenizer.Tokenize(code);

new TokensColorizer(tokens, changeLinePart, line).Colorize();
}

private void Colorize()
{
IImmutableBrush? brush;
while (_tokenStream.CanAdvance)
{
WalkLeadingTrivia(_tokenStream.Current);
brush = GetTokenBrush(_tokenStream.Current);
if (brush is not null)
SetForeground(_tokenStream.Current, brush);
_tokenStream.Advance();
}

WalkLeadingTrivia(_tokenStream.Current);
brush = GetTokenBrush(_tokenStream.Current);
if (brush is not null)
SetForeground(_tokenStream.Current, brush);
}

private void WalkLeadingTrivia(Token token)
{
foreach (var trivia in token.LeadingTrivia.Where(trivia => trivia.IsComment))
SetForeground(trivia, CommentBrush);
}

private void SetForeground(StringRange range, IBrush brush)
{
if (new StringRange(_line.Offset, _line.Length).Intersection(range) is { } intersection)
_changeLinePart.Invoke(intersection.Start, intersection.End,
e => e.TextRunProperties.SetForegroundBrush(brush));
}

private void SetForeground(Token token, IImmutableBrush brush)
{
WalkLeadingTrivia(token);
var tokenRange = token.Range;

if (token.IsString)
tokenRange = new StringRange(tokenRange.Start - 1, tokenRange.Length + 2);

SetForeground(tokenRange, brush);
}

private static IImmutableBrush? GetTokenBrush(Token token)
{
if (token.IsNumber)
return NumberBrush;

if (token.IsKeyWord)
return KeywordBrush;

if (token.IsString)
return StringBrush;

if (token.IsComment)
return CommentBrush;

return null;
}
}
28 changes: 28 additions & 0 deletions SimpleExecutor/Models/TokensSyntaxColorizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;
using SimpleExecutor.ViewModels;

namespace SimpleExecutor.Models;

public sealed class TokensSyntaxColorizer : DocumentColorizingTransformer
{
private readonly MainViewModel _viewModel;

public TokensSyntaxColorizer(MainViewModel viewModel)
{
_viewModel = viewModel;
}

protected override void ColorizeLine(DocumentLine line)
{
try
{
TokensColorizer.Colorize(_viewModel.Code, ChangeLinePart, line);
}
catch (Exception)
{
// ignored
}
}
}
Loading

0 comments on commit 71d236f

Please sign in to comment.