Skip to content

Commit 00a6edf

Browse files
authored
decide migration of multi rule applications based on edits (#119)
2 parents abbd818 + 140e025 commit 00a6edf

46 files changed

Lines changed: 135254 additions & 151 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using NMF.AnyText.Rules;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace NMF.AnyText
9+
{
10+
internal class ChangeTracker
11+
{
12+
private TextEdit[] _edits = Array.Empty<TextEdit>();
13+
14+
public void SetEdits(IEnumerable<TextEdit> edits)
15+
{
16+
if (edits.TryGetNonEnumeratedCount(out var count) && count <= 1)
17+
{
18+
_edits = edits as TextEdit[] ?? edits.ToArray();
19+
}
20+
else
21+
{
22+
_edits = edits.OrderBy(e => e.Start).ToArray();
23+
}
24+
}
25+
26+
public bool IsInsertion(RuleApplication ruleApplication, ParseContext context)
27+
{
28+
// TODO: implement binary search
29+
for (int i = 0; i < _edits.Length; i++)
30+
{
31+
var edit = _edits[i];
32+
if (ruleApplication.CurrentPosition >= edit.Start &&
33+
ruleApplication.CurrentPosition + ruleApplication.Length <= context.Matcher.NextTokenPosition(edit.Start + GetLength(edit)))
34+
{
35+
return true;
36+
}
37+
}
38+
return false;
39+
}
40+
41+
private ParsePositionDelta GetLength(TextEdit textEdit)
42+
{
43+
if (textEdit.NewText.Length == 0)
44+
{
45+
return default;
46+
}
47+
var lastLineLength = textEdit.NewText[textEdit.NewText.Length - 1].Length;
48+
return new ParsePositionDelta(textEdit.NewText.Length - 1, lastLineLength);
49+
}
50+
51+
public bool IsObsoleted(RuleApplication ruleApplication, ParseContext context)
52+
{
53+
if (context.Matcher.IsObsoleted(ruleApplication))
54+
{
55+
return true;
56+
}
57+
for (int i = 0; i < _edits.Length; i++)
58+
{
59+
var edit = _edits[i];
60+
var pos = ruleApplication.CurrentPosition;
61+
if (pos >= edit.Start)
62+
{
63+
pos = CompensateMatcherInsertions(pos, edit);
64+
if (pos + ruleApplication.Length <= context.Matcher.NextTokenPosition(edit.End))
65+
{
66+
return true;
67+
}
68+
}
69+
else
70+
{
71+
// edits are sorted, further edits cannot contain rule application
72+
return false;
73+
}
74+
}
75+
return false;
76+
}
77+
78+
private static ParsePosition CompensateMatcherInsertions(ParsePosition pos, TextEdit edit)
79+
{
80+
var linesDelta = edit.NewText.Length - (edit.End.Line - edit.Start.Line + 1);
81+
pos = new ParsePosition(pos.Line - linesDelta, pos.Col);
82+
return pos;
83+
}
84+
85+
internal void Reset() => SetEdits(Array.Empty<TextEdit>());
86+
}
87+
}

AnyText/AnyText.Core/Matcher.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public void Reset()
4141
_trailingComments = null;
4242
}
4343

44+
internal RuleApplication GetLiteralAt(ParsePosition position)
45+
{
46+
var col = GetLine(position.Line)?.GetOrCreateColumn(position.Col);
47+
return col?.Applications.Values.FirstOrDefault(r => r.Rule.IsLiteral && r.IsPositive);
48+
}
49+
4450
/// <summary>
4551
/// Gets the position of the next token, starting from the given position
4652
/// </summary>
@@ -49,7 +55,7 @@ public void Reset()
4955
public ParsePosition NextTokenPosition(ParsePosition position)
5056
{
5157
var line = position.Line;
52-
var col = position.Col + 1;
58+
var col = position.Col;
5359
while (line < _memoTable.Count)
5460
{
5561
var memoLine = _memoTable[line];
@@ -398,7 +404,12 @@ public RuleApplication Match(ParseContext context)
398404
public void Apply(TextEdit edit)
399405
{
400406
var refreshLineIndices = false;
401-
for (int i = 0; i <= edit.End.Line && i < _memoTable.Count; i++)
407+
var lastEffectiveLength = edit.End.Line;
408+
if (edit.End.Col == 0 && edit.NewText != null && edit.NewText.Length > 0 && edit.NewText[edit.NewText.Length - 1].Length == 0)
409+
{
410+
lastEffectiveLength--;
411+
}
412+
for (int i = 0; i <= lastEffectiveLength && i < _memoTable.Count; i++)
402413
{
403414
var line = GetLine(i);
404415

@@ -592,5 +603,17 @@ private static void MoveOverWhitespace(ParseContext context, ref ParsePosition p
592603
}
593604
position = new ParsePosition(lineNo, 0);
594605
}
606+
607+
/// <summary>
608+
/// Determines whether the given rule application is obsoleted
609+
/// </summary>
610+
/// <param name="ruleApplication">the rule application to check</param>
611+
/// <returns>true, if the rule application is on a line that has been obsoleted</returns>
612+
/// <exception cref="NotImplementedException"></exception>
613+
public bool IsObsoleted(RuleApplication ruleApplication)
614+
{
615+
var line = ruleApplication.Line;
616+
return line == null || (line.LineNo >= _memoTable.Count || _memoTable[line.LineNo] != line);
617+
}
595618
}
596619
}

AnyText/AnyText.Core/ParseContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public ParseContext(Grammar grammar, Matcher matcher, StringComparison stringCom
2828
ArgumentNullException.ThrowIfNull(grammar);
2929
ArgumentNullException.ThrowIfNull(matcher);
3030

31+
ChangeTracker = new ChangeTracker();
3132
Grammar = grammar;
3233
Matcher = matcher;
3334
StringComparison = stringComparison;
@@ -91,6 +92,8 @@ public void RefreshRoot()
9192
/// </summary>
9293
public Matcher Matcher { get; }
9394

95+
internal ChangeTracker ChangeTracker { get; }
96+
9497
/// <summary>
9598
/// Indicates whether the last update sent to the parser was successful
9699
/// </summary>

AnyText/AnyText.Core/Parser.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public object Initialize(string[] input, bool skipValidation = false)
7373
{
7474
_context.Input = input;
7575
_matcher.Reset();
76+
_context.ChangeTracker.Reset();
7677
try
7778
{
7879
_context.IsParsing = true;
@@ -115,7 +116,8 @@ public void Initialize(object semanticObject, bool skipValidation = false)
115116
{
116117
throw new ArgumentException("no parse tree could be created for this object.", nameof(semanticObject));
117118
}
118-
_context.Matcher.Reset();
119+
_matcher.Reset();
120+
_context.ChangeTracker.Reset();
119121
var writer = new StringWriter();
120122
var prettyWriter = new PrettyPrintWriter(writer, " ");
121123
ruleApplication.Write(prettyWriter, _context);
@@ -166,7 +168,7 @@ public object Update(TextEdit edit, bool skipValidation)
166168

167169
input = edit.Apply(input);
168170
_matcher.Apply(edit);
169-
171+
_context.ChangeTracker.SetEdits(new[] { edit });
170172
UpdateCore(input, skipValidation);
171173
}
172174
finally
@@ -204,6 +206,7 @@ public object Update(IEnumerable<TextEdit> edits, bool skipValidation)
204206
input = edit.Apply(input);
205207
_matcher.Apply(edit);
206208
}
209+
_context.ChangeTracker.SetEdits(edits);
207210
UpdateCore(input, skipValidation);
208211
return _context.Root;
209212
}
@@ -212,7 +215,6 @@ public object Update(IEnumerable<TextEdit> edits, bool skipValidation)
212215
Context.IsParsing = false;
213216
Context.IsExecutingModelChanges = false;
214217
}
215-
216218
}
217219

218220
/// <summary>
@@ -258,6 +260,8 @@ public IReadOnlyList<TextEdit> Update(object updatedElement)
258260
}
259261
}
260262
}
263+
// do not pass edits to change tracker because the parse tree already contains everything
264+
_context.ChangeTracker.Reset();
261265

262266
UpdateCore(input, false);
263267

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using System.Reflection;
22
using System.Runtime.InteropServices;
33
using System;
4+
using System.Runtime.CompilerServices;
45

56
[assembly: AssemblyTitle("NMF AnyText Core")]
67
[assembly: AssemblyDescription("A library with an incremental packrat parser")]
78
[assembly: AssemblyCopyright("Copyright © Georg Hinkel 2025")]
89
[assembly: Guid("C4B20D44-F618-40DE-8FF9-8147A958C681")]
910
[assembly: CLSCompliant(false)]
11+
12+
[assembly: InternalsVisibleTo("AnyText.Tests")]

AnyText/AnyText.Core/Rules/LiteralRuleApplication.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,20 @@ public override LiteralRuleApplication GetLastInnerLiteral()
137137
{
138138
return this;
139139
}
140+
141+
internal override RuleApplication MigrateTo(LiteralRuleApplication literal, ParseContext context)
142+
{
143+
if (literal.Rule != Rule || literal.Literal != Literal)
144+
{
145+
return base.MigrateTo(literal, context);
146+
}
147+
var old = Literal;
148+
Length = literal.Length;
149+
ExaminedTo = literal.ExaminedTo;
150+
Comments = literal.Comments;
151+
literal.ReplaceWith(this);
152+
153+
return this;
154+
}
140155
}
141156
}

0 commit comments

Comments
 (0)