-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bb570e5
commit 071a686
Showing
30 changed files
with
1,909 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks> | ||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> | ||
<OutputType>Library</OutputType> | ||
<AssemblyName>NMF.AnyText</AssemblyName> | ||
<RootNamespace>NMF.AnyText</RootNamespace> | ||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
<OutputPath>..\..\Build</OutputPath> | ||
</PropertyGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
using NMF.AnyText.Rules; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace NMF.AnyText | ||
{ | ||
/// <summary> | ||
/// Denotes a class that matches text using an incremental packrat parser | ||
/// </summary> | ||
public class Matcher | ||
{ | ||
private readonly List<MemoLine> _memoTable = new List<MemoLine>(); | ||
|
||
private MemoLine GetLine(int line) | ||
{ | ||
while (_memoTable.Count <= line) | ||
{ | ||
_memoTable.Add(new MemoLine()); | ||
} | ||
return _memoTable[line]; | ||
} | ||
|
||
private static IEnumerable<RuleApplication> GetCol(SortedDictionary<int, List<RuleApplication>> line, int col) | ||
{ | ||
if (line.TryGetValue(col, out var ruleApplications)) | ||
{ | ||
return ruleApplications; | ||
} | ||
return Enumerable.Empty<RuleApplication>(); | ||
} | ||
|
||
internal RuleApplication MatchCore(Rule rule, ParseContext context, ref ParsePosition position) | ||
{ | ||
var line = GetLine(position.Line); | ||
var ruleApplications = GetCol(line.Columns, position.Col); | ||
|
||
var ruleApplication = ruleApplications.FirstOrDefault(r => r.Rule == rule); | ||
if (ruleApplication == null) | ||
{ | ||
var col = position.Col; | ||
ruleApplication = rule.Match(context, ref position); | ||
if (!(ruleApplications is List<RuleApplication> ruleApplicationList)) | ||
{ | ||
// need to check again because another call could have created the column | ||
ruleApplicationList = GetCol(line.Columns, col) as List<RuleApplication>; | ||
if (ruleApplicationList == null) | ||
{ | ||
ruleApplicationList = new List<RuleApplication>(); | ||
line.Columns.Add(col, ruleApplicationList); | ||
} | ||
} | ||
line.MaxExaminedLength = ParsePositionDelta.Larger(line.MaxExaminedLength, PrependColOffset(ruleApplication.ExaminedTo, col)); | ||
ruleApplicationList.Add(ruleApplication); | ||
} | ||
else | ||
{ | ||
position += ruleApplication.Length; | ||
} | ||
if (rule.TrailingWhitespaces) | ||
{ | ||
RuleHelper.MoveOverWhitespace(context, ref position); | ||
} | ||
return ruleApplication; | ||
} | ||
|
||
private static ParsePositionDelta PrependColOffset(ParsePositionDelta examinedTo, int col) | ||
{ | ||
if (examinedTo.Line == 0) | ||
{ | ||
return new ParsePositionDelta(0, examinedTo.Col + col); | ||
} | ||
return examinedTo; | ||
} | ||
|
||
/// <summary> | ||
/// Matches the provided rule with the given parse context | ||
/// </summary> | ||
/// <param name="context">The context in which the text is parsed, including the current input</param> | ||
/// <returns>A rule application for the entire text</returns> | ||
public RuleApplication Match(ParseContext context) | ||
{ | ||
var position = new ParsePosition(0, 0); | ||
RuleHelper.MoveOverWhitespace(context, ref position); | ||
var match = MatchCore(context.RootRule, context, ref position); | ||
if (!match.IsPositive || position.Line == context.Input.Length) | ||
{ | ||
return match; | ||
} | ||
return new FailedRuleApplication(context.RootRule, new ParsePositionDelta(position.Line, position.Col), position, "Unexpected content"); | ||
} | ||
|
||
/// <summary> | ||
/// Removes any memoization based on the given text edit | ||
/// </summary> | ||
/// <param name="edit">The change in the input text</param> | ||
public void RemoveMemoizedRuleApplications(TextEdit edit) | ||
{ | ||
for (int i = 0; i <= edit.Start.Line; i++) | ||
{ | ||
var line = GetLine(i); | ||
|
||
if (new ParsePosition(i, 0) + line.MaxExaminedLength < edit.Start) | ||
{ | ||
continue; | ||
} | ||
|
||
var maxReach = default(ParsePositionDelta); | ||
|
||
foreach (var col in line.Columns) | ||
{ | ||
var pos = new ParsePosition(i, col.Key); | ||
|
||
for (int j = col.Value.Count - 1; j >= 0; j--) | ||
{ | ||
var ruleApplication = col.Value[j]; | ||
if (pos + ruleApplication.ExaminedTo < edit.Start) | ||
{ | ||
maxReach = ParsePositionDelta.Larger(maxReach, PrependColOffset(ruleApplication.ExaminedTo, col.Key)); | ||
} | ||
else | ||
{ | ||
col.Value.RemoveAt(j); | ||
} | ||
} | ||
} | ||
|
||
line.MaxExaminedLength = maxReach; | ||
} | ||
} | ||
|
||
private sealed class MemoLine | ||
{ | ||
public SortedDictionary<int, List<RuleApplication>> Columns { get; } = new SortedDictionary<int, List<RuleApplication>>(); | ||
|
||
public ParsePositionDelta MaxExaminedLength { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using NMF.AnyText.Rules; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace NMF.AnyText.Model | ||
{ | ||
/// <summary> | ||
/// Denotes a rule that adds the value of an inner rule to a collection of the semantic element | ||
/// </summary> | ||
/// <typeparam name="TContext">The type of the context element</typeparam> | ||
/// <typeparam name="TProperty">The type of the property value</typeparam> | ||
public abstract class AddAssignRule<TContext, TProperty> : QuoteRule | ||
{ | ||
/// <inheritdoc /> | ||
protected internal override void OnActivate(RuleApplication application, ParseContext context) | ||
{ | ||
if (application.ContextElement is TContext contextElement && application.GetValue(context) is TProperty propertyValue) | ||
{ | ||
GetCollection(contextElement, context).Add(propertyValue); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected internal override void OnDeactivate(RuleApplication application, ParseContext context) | ||
{ | ||
if (application.ContextElement is TContext contextElement && application.GetValue(context) is TProperty propertyValue) | ||
{ | ||
GetCollection(contextElement, context).Remove(propertyValue); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected internal override bool OnValueChange(RuleApplication application, ParseContext context) | ||
{ | ||
if (application.ContextElement is TContext contextElement && application.GetValue(context) is TProperty propertyValue) | ||
{ | ||
GetCollection(contextElement, context).Add(propertyValue); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Obtains the child collection | ||
/// </summary> | ||
/// <param name="semanticElement">the semantic element</param> | ||
/// <param name="context">the parse context in which the collection is obtained</param> | ||
/// <returns>a collection of values</returns> | ||
public abstract ICollection<TProperty> GetCollection(TContext semanticElement, ParseContext context); | ||
|
||
private sealed class AddAssignRuleApplication : SingleRuleApplication | ||
{ | ||
public AddAssignRuleApplication(Rule rule, RuleApplication inner, ParsePositionDelta endsAt, ParsePositionDelta examinedTo) : base(rule, inner, endsAt, examinedTo) | ||
{ | ||
} | ||
|
||
protected override void OnMigrate(RuleApplication oldValue, RuleApplication newValue, ParseContext context) | ||
{ | ||
if (oldValue.IsActive) | ||
{ | ||
oldValue.Deactivate(context); | ||
newValue.Activate(context); | ||
if (Rule is AddAssignRule<TContext, TProperty> addAssignRule && ContextElement is TContext contextElement) | ||
{ | ||
var collection = addAssignRule.GetCollection(contextElement, context); | ||
if (oldValue.GetValue(context) is TProperty oldProperty) | ||
{ | ||
collection.Remove(oldProperty); | ||
} | ||
if (newValue.GetValue(context) is TProperty newProperty) | ||
{ | ||
collection.Add(newProperty); | ||
} | ||
} | ||
else | ||
{ | ||
Rule.OnValueChange(this, context); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using NMF.AnyText.Rules; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace NMF.AnyText.Model | ||
{ | ||
/// <summary> | ||
/// Denotes a rule that assigns the value of a child rule to a certain property | ||
/// </summary> | ||
/// <typeparam name="TContext">The type of the context element</typeparam> | ||
/// <typeparam name="TProperty">The type of the property value</typeparam> | ||
public abstract class AssignRule<TContext, TProperty> : QuoteRule | ||
{ | ||
/// <inheritdoc /> | ||
protected internal override void OnActivate(RuleApplication application, ParseContext context) | ||
{ | ||
if (application.ContextElement is TContext contextElement && application.GetValue(context) is TProperty propertyValue) | ||
{ | ||
OnChangeValue(contextElement, propertyValue, context); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected internal override void OnDeactivate(RuleApplication application, ParseContext context) | ||
{ | ||
if (application.ContextElement is TContext contextElement) | ||
{ | ||
OnChangeValue(contextElement, default, context); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected internal override bool OnValueChange(RuleApplication application, ParseContext context) | ||
{ | ||
if (application.ContextElement is TContext contextElement && application.GetValue(context) is TProperty propertyValue) | ||
{ | ||
OnChangeValue(contextElement, propertyValue, context); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Gets called when the value changes | ||
/// </summary> | ||
/// <param name="semanticElement">the context element</param> | ||
/// <param name="propertyValue">the property value</param> | ||
/// <param name="context">the parsing context</param> | ||
protected abstract void OnChangeValue(TContext semanticElement, TProperty propertyValue, ParseContext context); | ||
|
||
} | ||
} |
Oops, something went wrong.