Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 128 additions & 8 deletions ExtensibleParaser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
private readonly HashSet<Terminal> _expected = [];
public Terminal Trivia { get; private set; } = trivia;
public Log? Logger { get; set; } = log;

[Conditional("TRACE")]
private void Log(string message, LogImportance importance = LogImportance.Normal, [CallerMemberName] string? memberName = null, [CallerLineNumber] int line = 0) =>
private void Log(string message, LogImportance importance = LogImportance.Normal, [CallerMemberName] string? memberName = null, [CallerLineNumber] int line = 0) =>
Logger?.Info($"{memberName} ({line}): {message}", importance);

public Dictionary<string, Rule[]> Rules { get; } = new();
Expand Down Expand Up @@ -159,9 +159,9 @@


if (ErrorPos <= oldErrorPos)
{
// Recovery in recovery rules mode failed.
// TODO: Implement panic mode recovery for this case.
{
// Recovery in recovery rules mode failed.
// TODO: Implement panic mode recovery for this case.

var debugInfos = MemoizationVisualazer(input);

Expand Down Expand Up @@ -320,7 +320,7 @@

if (bestPos == _recoverySkipPos)
{
_recoverySkipPos = -1;
_recoverySkipPos = -1;
Log($"Rule recovery finished {currentResult}. New pos: {bestPos}");
break;
}
Expand Down Expand Up @@ -375,7 +375,8 @@
Optional o => ParseOptional(o, startPos, input),
OftenMissed o => ParseOftenMissed(o, startPos, input),
AndPredicate a => ParseAndPredicate(a, startPos, input),
NotPredicate n => ParseNotPredicate(n, startPos, input),
NotPredicate n => ParseNotPredicate(n, startPos, input),
SeparatedList sl => ParseSeparatedList(sl, startPos, input),
_ => throw new IndexOutOfRangeException($"Unsupported rule type: {rule.GetType().Name}: {rule}")
};

Expand Down Expand Up @@ -524,7 +525,7 @@
maxFailPos: currentPos // ???
);

Result panicRecovery(Terminal terminal, int startPos, string input)

Check warning on line 528 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

The local function 'panicRecovery' is declared but never used

Check warning on line 528 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

The local function 'panicRecovery' is declared but never used

Check warning on line 528 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

The local function 'panicRecovery' is declared but never used
{
Log($"Starting recovery for terminal {terminal.Kind} at position {startPos}");
var currentRule = _ruleStack.Peek();
Expand Down Expand Up @@ -624,4 +625,123 @@

return Result.Success(new SeqNode(seq.Kind ?? "Seq", elements, startPos, newPos), newPos, maxFailPos);
}
}

private Result ParseSeparatedList(SeparatedList listRule, int startPos, string input)
{
Log($"Parsing at {startPos} SeparatedList: {listRule}");

var elements = new List<ISyntaxNode>();
int currentPos = startPos;
int maxFailPos = startPos;
var recoveryMode = currentPos == _recoverySkipPos;

// Обработка пустого списка
if (listRule.CanBeEmpty)
{
var firstElementResult = ParseAlternative(listRule.Element, currentPos, input);
if (firstElementResult.MaxFailPos > maxFailPos)
maxFailPos = firstElementResult.MaxFailPos;

if (!firstElementResult.IsSuccess)
return Result.Success(new ListNode(listRule.Kind, elements, startPos, currentPos), currentPos, maxFailPos);
}

// Первый элемент
var firstResult = ParseAlternative(listRule.Element, currentPos, input);
if (firstResult.MaxFailPos > maxFailPos)
maxFailPos = firstResult.MaxFailPos;

if (!firstResult.TryGetSuccess(out var firstNode, out currentPos))
{
if (listRule.CanBeEmpty)
return Result.Success(new ListNode(listRule.Kind, elements, startPos, startPos), startPos, maxFailPos);

Log($"SeparatedList: first element required at {currentPos}");
return Result.Failure(maxFailPos).WithRecoveryState(ErrorPos);
}
elements.Add(firstNode);

// Последующие элементы
while (true)
{
// Парсинг разделителя
var sepResult = ParseAlternative(listRule.Separator, currentPos, input);
if (sepResult.MaxFailPos > maxFailPos)
maxFailPos = sepResult.MaxFailPos;

if (!sepResult.TryGetSuccess(out var sepNode, out var sepPos))
{
if (listRule.IsSeparatorOptional)
{
// В режиме восстановления пропускаем проверку разделителя
if (recoveryMode) break;

// Пытаемся найти элемент без разделителя
var elemResult1 = ParseAlternative(listRule.Element, currentPos, input);
if (elemResult1.MaxFailPos > maxFailPos)
maxFailPos = elemResult1.MaxFailPos;

if (!elemResult1.TryGetSuccess(out var elemNode1, out _))
break;

elements.Add(elemNode1);
}
else
{
// Восстановление: пропускаем до следующего элемента
if (currentPos >= ErrorPos)
{
_expected.Add(listRule.Separator as Terminal);

Check warning on line 694 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

Possible null reference argument for parameter 'item' in 'bool HashSet<Terminal>.Add(Terminal item)'.

Check warning on line 694 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

Possible null reference argument for parameter 'item' in 'bool HashSet<Terminal>.Add(Terminal item)'.

Check warning on line 694 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

Possible null reference argument for parameter 'item' in 'bool HashSet<Terminal>.Add(Terminal item)'.
ErrorPos = currentPos;
}
Log($"Missing separator at {currentPos}. Recovery mode: {recoveryMode}");
return Result.Failure(maxFailPos);
}
continue;
}

// Парсинг элемента после разделителя
var elemResult = ParseAlternative(listRule.Element, sepPos, input);
if (elemResult.MaxFailPos > maxFailPos)
maxFailPos = elemResult.MaxFailPos;

if (!elemResult.TryGetSuccess(out var elemNode, out currentPos))
{
// Восстановление: создаем ошибку для пропущенного элемента
if (currentPos >= ErrorPos)
{
_expected.Add(listRule.Element as Terminal);

Check warning on line 713 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

Possible null reference argument for parameter 'item' in 'bool HashSet<Terminal>.Add(Terminal item)'.

Check warning on line 713 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

Possible null reference argument for parameter 'item' in 'bool HashSet<Terminal>.Add(Terminal item)'.

Check warning on line 713 in ExtensibleParaser/Parser.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

Possible null reference argument for parameter 'item' in 'bool HashSet<Terminal>.Add(Terminal item)'.
ErrorPos = sepPos;
}
var errorNode = new TerminalNode("Error", sepPos, sepPos, 0, IsRecovery: true);
elements.Add(errorNode);
Log($"Missing element after separator at {sepPos}");
return Result.Success(
new ListNode(listRule.Kind, elements, startPos, sepPos),
sepPos,
maxFailPos
).WithRecoveryState(ErrorPos);
}

elements.Add(sepNode); // Добавляем разделитель как часть AST
elements.Add(elemNode);
}

return Result.Success(
new ListNode(listRule.Kind, elements, startPos, currentPos),
currentPos,
maxFailPos
);
}
}

public static class Ext
{
public static Result WithRecoveryState(this Result result, int errorPos)
{
if (result.TryGetSuccess(out var node, out var pos))
return Result.Success(node, pos, Math.Max(result.MaxFailPos, errorPos));

return Result.Failure(Math.Max(result.MaxFailPos, errorPos));
}
}
31 changes: 30 additions & 1 deletion ExtensibleParaser/Rules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,40 @@ public override IEnumerable<Rule> GetSubRules<T>()
}
}

public record SeparatedList(
Rule Element,
Rule Separator,
bool IsSeparatorOptional,
bool CanBeEmpty,
string? Kind = null
) : Rule(Kind ?? nameof(SeparatedList))
{
public override string ToString() => $"({Element}; {Separator}{(IsSeparatorOptional ? "?" : "")})*{(CanBeEmpty ? "" : "+")}";

public override Rule InlineReferences(Dictionary<string, Rule> inlineableRules)
{
var inlinedElement = Element.InlineReferences(inlineableRules);
var inlinedSeparator = Separator.InlineReferences(inlineableRules);
return new SeparatedList(inlinedElement, inlinedSeparator, IsSeparatorOptional, CanBeEmpty, Kind);
}

public override IEnumerable<Rule> GetSubRules<T>()
{
if (this is T)
yield return this;
foreach (var subRule in Element.GetSubRules<T>())
yield return subRule;

// Мб. не требуется.
foreach (var subRule in Separator.GetSubRules<T>())
yield return subRule;
}
}

public abstract record RecoveryTerminal(string Kind) : Terminal(Kind);

public record EmptyTerminal(string Kind) : RecoveryTerminal(Kind)
{
public override int TryMatch(string input, int position) => 0;
public override string ToString() => Kind;
}

10 changes: 10 additions & 0 deletions ExtensibleParaser/SyntaxTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface ISyntaxVisitor
{
void Visit(TerminalNode node);
void Visit(SeqNode node);
void Visit(ListNode node);
void Visit(SomeNode node);
void Visit(NoneNode node);
}
Expand Down Expand Up @@ -75,6 +76,15 @@ public record SeqNode(string Kind, IReadOnlyList<ISyntaxNode> Elements, int Star
public override void Accept(ISyntaxVisitor visitor) => visitor.Visit(this);
}

public record ListNode(string Kind, IReadOnlyList<ISyntaxNode> Elements, int StartPos, int EndPos, bool HasTrailingSeparator = false, bool IsRecovery = false)
: Node(Kind, StartPos, EndPos, IsRecovery)
{
public override void Accept(ISyntaxVisitor visitor) => visitor.Visit(this);

public override string ToString(string input) =>
$"[{string.Join(", ", Elements.Select(e => e.ToString(input)))}]";
}

public abstract record OptionalNode(string Kind, int StartPos, int EndPos) : Node(Kind, StartPos, EndPos);

public record SomeNode(string Kind, ISyntaxNode Value, int StartPos, int EndPos)
Expand Down
9 changes: 5 additions & 4 deletions Nitra.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35825.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensibleParaser", "ExtensibleParaser\ExtensibleParaser.csproj", "{DA07822B-4292-43D0-BF14-DBA54FFC042C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtensibleParaser", "ExtensibleParaser\ExtensibleParaser.csproj", "{DA07822B-4292-43D0-BF14-DBA54FFC042C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{F4425031-73A3-42A6-92B1-4877832C18B2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{F4425031-73A3-42A6-92B1-4877832C18B2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
global.json = global.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGenerator", "TerminalGenerator\TerminalGenerator.csproj", "{688DD31C-5C27-4FB9-851C-21DE6A83E624}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalGenerator", "TerminalGenerator\TerminalGenerator.csproj", "{688DD31C-5C27-4FB9-851C-21DE6A83E624}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Regex", "Regex\Regex\Regex.csproj", "{2F33393E-1EC0-55C8-592E-3F21F33DBC23}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Regex", "Regex\Regex\Regex.csproj", "{2F33393E-1EC0-55C8-592E-3F21F33DBC23}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
11 changes: 11 additions & 0 deletions Tests/Calc/CalcTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ public void Visit(SeqNode node)
};
}

public void Visit(ListNode node)
{
var children = new List<Expr>();
foreach (var element in node.Elements)
{
element.Accept(this);
if (Result != null)
children.Add(Result);
}
}

public void Visit(SomeNode node) => node.Value.Accept(this);
public void Visit(NoneNode node) => Result = null;
}
Expand Down
10 changes: 9 additions & 1 deletion Tests/MiniC/MiniCTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void Initialize()
Terminals.Ident(),
new Seq([new Literal("("), new Ref("Expr"), ], "Parens"),
new Seq([Terminals.Ident(), new Literal("("), closingBracket], "CallNoArgs"),
new Seq([Terminals.Ident(), new Literal("("), new Ref("Expr"), new ZeroOrMany(new Seq([new Literal(","), new Ref("Expr")], Kind: "ArgsRest")), closingBracket], "Call"),
new Seq([Terminals.Ident(), new Literal("("), new SeparatedList(new Ref("Expr"), new Literal(","), IsSeparatorOptional: true, CanBeEmpty: true, Kind: "ArgsRest"), closingBracket], "Call"),
new Seq([new Literal("-"), new ReqRef("Expr", 300)], "Neg"),

new Seq([new Ref("Expr"), new Literal("*"), new ReqRef("Expr", 200)], "Mul"),
Expand Down Expand Up @@ -372,6 +372,14 @@ public void FunctionCallNoArgs() => TestMiniC(
"Call: func()"
);


[TestMethod]
public void FunctionCallWithOneArgs() => TestMiniC(
"Expr",
"func(1)",
"Call: func(1)"
);

[TestMethod]
public void FunctionCallWithArgs() => TestMiniC(
"Expr",
Expand Down
46 changes: 33 additions & 13 deletions Tests/MiniC/MiniCVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ public void Visit(SeqNode node)
"RecoveryEmptyOperator" => makeMissingBinaryOperator(children),
"Neg" => new UnaryExpr("-", (Expr)children[1]!),
"AssignmentExpr" => new BinaryExpr("=", (Expr)children[0]!, (Expr)children[2]!),
"ArgsRest" => children[1]!, // Just return the expression part (skip the comma)
"ParamsRest" => children[1]!, // Just return the parameter part (skip the comma)
_ => throw new InvalidOperationException($"Unknown sequence: {node}: «{node.AsSpan(Input)}»")
};
Expand Down Expand Up @@ -160,26 +159,47 @@ static Params handleParamsList(Ast?[] children)
static List<Expr> getCallArguments(IReadOnlyList<Ast?> children)
{
var args = new List<Expr>();
// First argument is after '('
if (children.Count > 2 && children[2] is Expr firstArg)
if (children.Count == 4 && children[2] is Args listArgs)
{
args.Add(firstArg);
args.AddRange(listArgs.Arguments);
}

// Additional arguments are in ZeroOrMany nodes
for (int i = 3; i < children.Count - 1; i++)
return args;
}
}

public void Visit(ListNode node)
{
var cleanedElements = new List<Ast>();
bool expectValue = true;

foreach (var element in node.Elements)
{
element.Accept(this);

if (expectValue)
{
if (children[i] is Block block)
{
args.AddRange(block.Statements.OfType<Expr>());
}
else if (children[i] is Expr expr)
if (Result is Expr expr)
{
args.Add(expr);
cleanedElements.Add(expr);
expectValue = false;
}
}
return args;
else
{
// Пропускаем разделители
expectValue = true;
}
}

Result = node.Kind switch
{
"ParamsRest" => new Params(cleanedElements.OfType<Identifier>().ToList()),
"ArgsRest" => new Args(cleanedElements.OfType<Expr>().ToList()),
_ => throw new InvalidOperationException($"Unknown sequence: {node}: «{node.AsSpan(Input)}»")
};

Trace.WriteLine($"Processed list with {cleanedElements.Count} elements");
}

public void Visit(SomeNode node) => node.Value.Accept(this);
Expand Down