feat: parse content text (base case).
This commit is contained in:
34
InkBlot.Tests/ContentTextTest.cs
Normal file
34
InkBlot.Tests/ContentTextTest.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using InkBlot.ParseHierarchy;
|
||||
using Shouldly;
|
||||
|
||||
namespace InkBlot.Tests;
|
||||
|
||||
public class ContentTextTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Hello!", "Hello!", 0)]
|
||||
[InlineData("Hel-lo!", "Hel-lo!", 0)]
|
||||
[InlineData("Hel\\lo!", "Hello!", 0)]
|
||||
[InlineData("Hel<lo!", "Hel<lo!", 0)]
|
||||
// TODO: check error situations better when diverts (->), threads (<-) and tags (#) are supported
|
||||
[InlineData("Hel->lo!", "", 1)]
|
||||
[InlineData("Hel<-lo!", "", 1)]
|
||||
[InlineData("Hel#lo!", "", 1)]
|
||||
public void TestBaseContentSuccess(string inkInput, string result, int numErrors)
|
||||
{
|
||||
// parse the story
|
||||
var fileReader = new PreMadeFileReader([
|
||||
("main.ink", inkInput)
|
||||
]);
|
||||
var parser = new InkBlotParser();
|
||||
var (story, diagnostics) = parser.Parse(fileReader, "main.ink");
|
||||
|
||||
// check the diagnostic counts match
|
||||
diagnostics.Count().ShouldBe(numErrors);
|
||||
|
||||
// check the contents match (only if there was no diagnostic)
|
||||
if (numErrors != 0) return;
|
||||
var contents = story.StoryNodes.ToArray();
|
||||
contents.ShouldBe([new Content(result)]);
|
||||
}
|
||||
}
|
||||
15
InkBlot.Tests/Helpers.cs
Normal file
15
InkBlot.Tests/Helpers.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Text;
|
||||
|
||||
namespace InkBlot.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// A file reader where the contents are directly provided as strings.
|
||||
/// </summary>
|
||||
/// <param name="filesToContents">A map between file names and their contents.</param>
|
||||
internal class PreMadeFileReader((string, string)[] filesToContents) : IFileReader
|
||||
{
|
||||
public Stream GetContents(string filename)
|
||||
{
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(filesToContents.First(e => e.Item1 == filename).Item2));
|
||||
}
|
||||
}
|
||||
26
InkBlot.Tests/InkBlot.Tests.csproj
Normal file
26
InkBlot.Tests/InkBlot.Tests.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0"/>
|
||||
<PackageReference Include="xunit" Version="2.9.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InkBlot\InkBlot.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
#
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkBlot", "InkBlot\InkBlot.csproj", "{EDFCA854-0AF5-4C8F-8820-C328915B0FFE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkBlotTestConsoleApp", "InkBlotTestConsoleApp\InkBlotTestConsoleApp.csproj", "{1BD86CDC-A93C-44EE-AA6B-E87E57FBC8AE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkBlot.Tests", "InkBlot.Tests\InkBlot.Tests.csproj", "{1B83421E-8A2D-4862-9E84-D8E5EDE13264}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -18,5 +21,9 @@ Global
|
||||
{1BD86CDC-A93C-44EE-AA6B-E87E57FBC8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1BD86CDC-A93C-44EE-AA6B-E87E57FBC8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1BD86CDC-A93C-44EE-AA6B-E87E57FBC8AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1B83421E-8A2D-4862-9E84-D8E5EDE13264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1B83421E-8A2D-4862-9E84-D8E5EDE13264}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1B83421E-8A2D-4862-9E84-D8E5EDE13264}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1B83421E-8A2D-4862-9E84-D8E5EDE13264}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
3
InkBlot/Assembly.cs
Normal file
3
InkBlot/Assembly.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("InkBlot.Tests")]
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace InkBlot;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
5
InkBlot/Error.cs
Normal file
5
InkBlot/Error.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace InkBlot;
|
||||
|
||||
public record Range(int StartLine, int StartColumn, int EndLine, int EndColumn);
|
||||
|
||||
public record Diagnostic(string FileName, Range Range, string Message, string Context);
|
||||
17
InkBlot/Generated/InkBlotAntlrGrammar.interp
Normal file
17
InkBlot/Generated/InkBlotAntlrGrammar.interp
Normal file
@@ -0,0 +1,17 @@
|
||||
token literal names:
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
Whitespace
|
||||
CONTENT_TEXT_NO_ESCAPE_SIMPLE
|
||||
|
||||
rule names:
|
||||
story
|
||||
contentText
|
||||
|
||||
|
||||
atn:
|
||||
[4, 1, 2, 12, 2, 0, 7, 0, 2, 1, 7, 1, 1, 0, 4, 0, 6, 8, 0, 11, 0, 12, 0, 7, 1, 1, 1, 1, 1, 1, 0, 0, 2, 0, 2, 0, 0, 10, 0, 5, 1, 0, 0, 0, 2, 9, 1, 0, 0, 0, 4, 6, 3, 2, 1, 0, 5, 4, 1, 0, 0, 0, 6, 7, 1, 0, 0, 0, 7, 5, 1, 0, 0, 0, 7, 8, 1, 0, 0, 0, 8, 1, 1, 0, 0, 0, 9, 10, 5, 2, 0, 0, 10, 3, 1, 0, 0, 0, 1, 7]
|
||||
2
InkBlot/Generated/InkBlotAntlrGrammar.tokens
Normal file
2
InkBlot/Generated/InkBlotAntlrGrammar.tokens
Normal file
@@ -0,0 +1,2 @@
|
||||
Whitespace=1
|
||||
CONTENT_TEXT_NO_ESCAPE_SIMPLE=2
|
||||
75
InkBlot/Generated/InkBlotAntlrGrammarBaseListener.cs
Normal file
75
InkBlot/Generated/InkBlotAntlrGrammarBaseListener.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// ANTLR Version: 4.13.2
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Generated from E:/ProgettiUnity/InkAntlr/InkBlot/InkBlot/InkBlotAntlrGrammar.g4 by ANTLR 4.13.2
|
||||
|
||||
// Unreachable code detected
|
||||
#pragma warning disable 0162
|
||||
// The variable '...' is assigned but its value is never used
|
||||
#pragma warning disable 0219
|
||||
// Missing XML comment for publicly visible type or member '...'
|
||||
#pragma warning disable 1591
|
||||
// Ambiguous reference in cref attribute
|
||||
#pragma warning disable 419
|
||||
|
||||
|
||||
using Antlr4.Runtime.Misc;
|
||||
using IErrorNode = Antlr4.Runtime.Tree.IErrorNode;
|
||||
using ITerminalNode = Antlr4.Runtime.Tree.ITerminalNode;
|
||||
using IToken = Antlr4.Runtime.IToken;
|
||||
using ParserRuleContext = Antlr4.Runtime.ParserRuleContext;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides an empty implementation of <see cref="IInkBlotAntlrGrammarListener"/>,
|
||||
/// which can be extended to create a listener which only needs to handle a subset
|
||||
/// of the available methods.
|
||||
/// </summary>
|
||||
[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
[System.CLSCompliant(false)]
|
||||
public partial class InkBlotAntlrGrammarBaseListener : IInkBlotAntlrGrammarListener {
|
||||
/// <summary>
|
||||
/// Enter a parse tree produced by <see cref="InkBlotAntlrGrammarParser.story"/>.
|
||||
/// <para>The default implementation does nothing.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
public virtual void EnterStory([NotNull] InkBlotAntlrGrammarParser.StoryContext context) { }
|
||||
/// <summary>
|
||||
/// Exit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.story"/>.
|
||||
/// <para>The default implementation does nothing.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
public virtual void ExitStory([NotNull] InkBlotAntlrGrammarParser.StoryContext context) { }
|
||||
/// <summary>
|
||||
/// Enter a parse tree produced by <see cref="InkBlotAntlrGrammarParser.contentText"/>.
|
||||
/// <para>The default implementation does nothing.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
public virtual void EnterContentText([NotNull] InkBlotAntlrGrammarParser.ContentTextContext context) { }
|
||||
/// <summary>
|
||||
/// Exit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.contentText"/>.
|
||||
/// <para>The default implementation does nothing.</para>
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
public virtual void ExitContentText([NotNull] InkBlotAntlrGrammarParser.ContentTextContext context) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>The default implementation does nothing.</remarks>
|
||||
public virtual void EnterEveryRule([NotNull] ParserRuleContext context) { }
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>The default implementation does nothing.</remarks>
|
||||
public virtual void ExitEveryRule([NotNull] ParserRuleContext context) { }
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>The default implementation does nothing.</remarks>
|
||||
public virtual void VisitTerminal([NotNull] ITerminalNode node) { }
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>The default implementation does nothing.</remarks>
|
||||
public virtual void VisitErrorNode([NotNull] IErrorNode node) { }
|
||||
}
|
||||
57
InkBlot/Generated/InkBlotAntlrGrammarBaseVisitor.cs
Normal file
57
InkBlot/Generated/InkBlotAntlrGrammarBaseVisitor.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// ANTLR Version: 4.13.2
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Generated from E:/ProgettiUnity/InkAntlr/InkBlot/InkBlot/InkBlotAntlrGrammar.g4 by ANTLR 4.13.2
|
||||
|
||||
// Unreachable code detected
|
||||
#pragma warning disable 0162
|
||||
// The variable '...' is assigned but its value is never used
|
||||
#pragma warning disable 0219
|
||||
// Missing XML comment for publicly visible type or member '...'
|
||||
#pragma warning disable 1591
|
||||
// Ambiguous reference in cref attribute
|
||||
#pragma warning disable 419
|
||||
|
||||
using Antlr4.Runtime.Misc;
|
||||
using Antlr4.Runtime.Tree;
|
||||
using IToken = Antlr4.Runtime.IToken;
|
||||
using ParserRuleContext = Antlr4.Runtime.ParserRuleContext;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides an empty implementation of <see cref="IInkBlotAntlrGrammarVisitor{Result}"/>,
|
||||
/// which can be extended to create a visitor which only needs to handle a subset
|
||||
/// of the available methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="Result">The return type of the visit operation.</typeparam>
|
||||
[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
[System.CLSCompliant(false)]
|
||||
public partial class InkBlotAntlrGrammarBaseVisitor<Result> : AbstractParseTreeVisitor<Result>, IInkBlotAntlrGrammarVisitor<Result> {
|
||||
/// <summary>
|
||||
/// Visit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.story"/>.
|
||||
/// <para>
|
||||
/// The default implementation returns the result of calling <see cref="AbstractParseTreeVisitor{Result}.VisitChildren(IRuleNode)"/>
|
||||
/// on <paramref name="context"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
/// <return>The visitor result.</return>
|
||||
public virtual Result VisitStory([NotNull] InkBlotAntlrGrammarParser.StoryContext context) { return VisitChildren(context); }
|
||||
/// <summary>
|
||||
/// Visit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.contentText"/>.
|
||||
/// <para>
|
||||
/// The default implementation returns the result of calling <see cref="AbstractParseTreeVisitor{Result}.VisitChildren(IRuleNode)"/>
|
||||
/// on <paramref name="context"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
/// <return>The visitor result.</return>
|
||||
public virtual Result VisitContentText([NotNull] InkBlotAntlrGrammarParser.ContentTextContext context) { return VisitChildren(context); }
|
||||
}
|
||||
120
InkBlot/Generated/InkBlotAntlrGrammarLexer.cs
Normal file
120
InkBlot/Generated/InkBlotAntlrGrammarLexer.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// ANTLR Version: 4.13.2
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Generated from E:/ProgettiUnity/InkAntlr/InkBlot/InkBlot/InkBlotAntlrGrammar.g4 by ANTLR 4.13.2
|
||||
|
||||
// Unreachable code detected
|
||||
#pragma warning disable 0162
|
||||
// The variable '...' is assigned but its value is never used
|
||||
#pragma warning disable 0219
|
||||
// Missing XML comment for publicly visible type or member '...'
|
||||
#pragma warning disable 1591
|
||||
// Ambiguous reference in cref attribute
|
||||
#pragma warning disable 419
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Antlr4.Runtime;
|
||||
using Antlr4.Runtime.Atn;
|
||||
using Antlr4.Runtime.Misc;
|
||||
using DFA = Antlr4.Runtime.Dfa.DFA;
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
|
||||
[System.CLSCompliant(false)]
|
||||
public partial class InkBlotAntlrGrammarLexer : Lexer {
|
||||
protected static DFA[] decisionToDFA;
|
||||
protected static PredictionContextCache sharedContextCache = new PredictionContextCache();
|
||||
public const int
|
||||
Whitespace=1, CONTENT_TEXT_NO_ESCAPE_SIMPLE=2;
|
||||
public static string[] channelNames = {
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
|
||||
};
|
||||
|
||||
public static string[] modeNames = {
|
||||
"DEFAULT_MODE"
|
||||
};
|
||||
|
||||
public static readonly string[] ruleNames = {
|
||||
"Whitespace", "CONTENT_TEXT_NO_ESCAPE_SIMPLE"
|
||||
};
|
||||
|
||||
|
||||
public InkBlotAntlrGrammarLexer(ICharStream input)
|
||||
: this(input, Console.Out, Console.Error) { }
|
||||
|
||||
public InkBlotAntlrGrammarLexer(ICharStream input, TextWriter output, TextWriter errorOutput)
|
||||
: base(input, output, errorOutput)
|
||||
{
|
||||
Interpreter = new LexerATNSimulator(this, _ATN, decisionToDFA, sharedContextCache);
|
||||
}
|
||||
|
||||
private static readonly string[] _LiteralNames = {
|
||||
};
|
||||
private static readonly string[] _SymbolicNames = {
|
||||
null, "Whitespace", "CONTENT_TEXT_NO_ESCAPE_SIMPLE"
|
||||
};
|
||||
public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames);
|
||||
|
||||
[NotNull]
|
||||
public override IVocabulary Vocabulary
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultVocabulary;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GrammarFileName { get { return "InkBlotAntlrGrammar.g4"; } }
|
||||
|
||||
public override string[] RuleNames { get { return ruleNames; } }
|
||||
|
||||
public override string[] ChannelNames { get { return channelNames; } }
|
||||
|
||||
public override string[] ModeNames { get { return modeNames; } }
|
||||
|
||||
public override int[] SerializedAtn { get { return _serializedATN; } }
|
||||
|
||||
static InkBlotAntlrGrammarLexer() {
|
||||
decisionToDFA = new DFA[_ATN.NumberOfDecisions];
|
||||
for (int i = 0; i < _ATN.NumberOfDecisions; i++) {
|
||||
decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
public override bool Sempred(RuleContext _localctx, int ruleIndex, int predIndex) {
|
||||
switch (ruleIndex) {
|
||||
case 1 : return CONTENT_TEXT_NO_ESCAPE_SIMPLE_sempred(_localctx, predIndex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private bool CONTENT_TEXT_NO_ESCAPE_SIMPLE_sempred(RuleContext _localctx, int predIndex) {
|
||||
switch (predIndex) {
|
||||
case 0: return InputStream.LA(1) != '>' ;
|
||||
case 1: return InputStream.LA(1) != '-' && InputStream.LA(1) != '>' ;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int[] _serializedATN = {
|
||||
4,0,2,21,6,-1,2,0,7,0,2,1,7,1,1,0,4,0,7,8,0,11,0,12,0,8,1,1,1,1,1,1,1,
|
||||
1,1,1,1,1,1,1,4,1,18,8,1,11,1,12,1,19,0,0,2,1,1,3,2,1,0,3,2,0,9,9,32,32,
|
||||
5,0,10,10,13,13,35,60,92,92,123,125,1,0,0,65535,25,0,1,1,0,0,0,0,3,1,0,
|
||||
0,0,1,6,1,0,0,0,3,17,1,0,0,0,5,7,7,0,0,0,6,5,1,0,0,0,7,8,1,0,0,0,8,6,1,
|
||||
0,0,0,8,9,1,0,0,0,9,2,1,0,0,0,10,18,8,1,0,0,11,12,5,92,0,0,12,18,7,2,0,
|
||||
0,13,14,5,45,0,0,14,18,4,1,0,0,15,16,5,60,0,0,16,18,4,1,1,0,17,10,1,0,
|
||||
0,0,17,11,1,0,0,0,17,13,1,0,0,0,17,15,1,0,0,0,18,19,1,0,0,0,19,17,1,0,
|
||||
0,0,19,20,1,0,0,0,20,4,1,0,0,0,4,0,8,17,19,0
|
||||
};
|
||||
|
||||
public static readonly ATN _ATN =
|
||||
new ATNDeserializer().Deserialize(_serializedATN);
|
||||
|
||||
|
||||
}
|
||||
23
InkBlot/Generated/InkBlotAntlrGrammarLexer.interp
Normal file
23
InkBlot/Generated/InkBlotAntlrGrammarLexer.interp
Normal file
@@ -0,0 +1,23 @@
|
||||
token literal names:
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
Whitespace
|
||||
CONTENT_TEXT_NO_ESCAPE_SIMPLE
|
||||
|
||||
rule names:
|
||||
Whitespace
|
||||
CONTENT_TEXT_NO_ESCAPE_SIMPLE
|
||||
|
||||
channel names:
|
||||
DEFAULT_TOKEN_CHANNEL
|
||||
HIDDEN
|
||||
|
||||
mode names:
|
||||
DEFAULT_MODE
|
||||
|
||||
atn:
|
||||
[4, 0, 2, 21, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 1, 0, 4, 0, 7, 8, 0, 11, 0, 12, 0, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 18, 8, 1, 11, 1, 12, 1, 19, 0, 0, 2, 1, 1, 3, 2, 1, 0, 3, 2, 0, 9, 9, 32, 32, 5, 0, 10, 10, 13, 13, 35, 60, 92, 92, 123, 125, 1, 0, 0, 65535, 25, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 1, 6, 1, 0, 0, 0, 3, 17, 1, 0, 0, 0, 5, 7, 7, 0, 0, 0, 6, 5, 1, 0, 0, 0, 7, 8, 1, 0, 0, 0, 8, 6, 1, 0, 0, 0, 8, 9, 1, 0, 0, 0, 9, 2, 1, 0, 0, 0, 10, 18, 8, 1, 0, 0, 11, 12, 5, 92, 0, 0, 12, 18, 7, 2, 0, 0, 13, 14, 5, 45, 0, 0, 14, 18, 4, 1, 0, 0, 15, 16, 5, 60, 0, 0, 16, 18, 4, 1, 1, 0, 17, 10, 1, 0, 0, 0, 17, 11, 1, 0, 0, 0, 17, 13, 1, 0, 0, 0, 17, 15, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 4, 1, 0, 0, 0, 4, 0, 8, 17, 19, 0]
|
||||
2
InkBlot/Generated/InkBlotAntlrGrammarLexer.tokens
Normal file
2
InkBlot/Generated/InkBlotAntlrGrammarLexer.tokens
Normal file
@@ -0,0 +1,2 @@
|
||||
Whitespace=1
|
||||
CONTENT_TEXT_NO_ESCAPE_SIMPLE=2
|
||||
53
InkBlot/Generated/InkBlotAntlrGrammarListener.cs
Normal file
53
InkBlot/Generated/InkBlotAntlrGrammarListener.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// ANTLR Version: 4.13.2
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Generated from E:/ProgettiUnity/InkAntlr/InkBlot/InkBlot/InkBlotAntlrGrammar.g4 by ANTLR 4.13.2
|
||||
|
||||
// Unreachable code detected
|
||||
#pragma warning disable 0162
|
||||
// The variable '...' is assigned but its value is never used
|
||||
#pragma warning disable 0219
|
||||
// Missing XML comment for publicly visible type or member '...'
|
||||
#pragma warning disable 1591
|
||||
// Ambiguous reference in cref attribute
|
||||
#pragma warning disable 419
|
||||
|
||||
using Antlr4.Runtime.Misc;
|
||||
using IParseTreeListener = Antlr4.Runtime.Tree.IParseTreeListener;
|
||||
using IToken = Antlr4.Runtime.IToken;
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines a complete listener for a parse tree produced by
|
||||
/// <see cref="InkBlotAntlrGrammarParser"/>.
|
||||
/// </summary>
|
||||
[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
|
||||
[System.CLSCompliant(false)]
|
||||
public interface IInkBlotAntlrGrammarListener : IParseTreeListener {
|
||||
/// <summary>
|
||||
/// Enter a parse tree produced by <see cref="InkBlotAntlrGrammarParser.story"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
void EnterStory([NotNull] InkBlotAntlrGrammarParser.StoryContext context);
|
||||
/// <summary>
|
||||
/// Exit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.story"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
void ExitStory([NotNull] InkBlotAntlrGrammarParser.StoryContext context);
|
||||
/// <summary>
|
||||
/// Enter a parse tree produced by <see cref="InkBlotAntlrGrammarParser.contentText"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
void EnterContentText([NotNull] InkBlotAntlrGrammarParser.ContentTextContext context);
|
||||
/// <summary>
|
||||
/// Exit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.contentText"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
void ExitContentText([NotNull] InkBlotAntlrGrammarParser.ContentTextContext context);
|
||||
}
|
||||
205
InkBlot/Generated/InkBlotAntlrGrammarParser.cs
Normal file
205
InkBlot/Generated/InkBlotAntlrGrammarParser.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// ANTLR Version: 4.13.2
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Generated from E:/ProgettiUnity/InkAntlr/InkBlot/InkBlot/InkBlotAntlrGrammar.g4 by ANTLR 4.13.2
|
||||
|
||||
// Unreachable code detected
|
||||
#pragma warning disable 0162
|
||||
// The variable '...' is assigned but its value is never used
|
||||
#pragma warning disable 0219
|
||||
// Missing XML comment for publicly visible type or member '...'
|
||||
#pragma warning disable 1591
|
||||
// Ambiguous reference in cref attribute
|
||||
#pragma warning disable 419
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using Antlr4.Runtime;
|
||||
using Antlr4.Runtime.Atn;
|
||||
using Antlr4.Runtime.Misc;
|
||||
using Antlr4.Runtime.Tree;
|
||||
using DFA = Antlr4.Runtime.Dfa.DFA;
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
|
||||
[System.CLSCompliant(false)]
|
||||
public partial class InkBlotAntlrGrammarParser : Parser {
|
||||
protected static DFA[] decisionToDFA;
|
||||
protected static PredictionContextCache sharedContextCache = new PredictionContextCache();
|
||||
public const int
|
||||
Whitespace=1, CONTENT_TEXT_NO_ESCAPE_SIMPLE=2;
|
||||
public const int
|
||||
RULE_story = 0, RULE_contentText = 1;
|
||||
public static readonly string[] ruleNames = {
|
||||
"story", "contentText"
|
||||
};
|
||||
|
||||
private static readonly string[] _LiteralNames = {
|
||||
};
|
||||
private static readonly string[] _SymbolicNames = {
|
||||
null, "Whitespace", "CONTENT_TEXT_NO_ESCAPE_SIMPLE"
|
||||
};
|
||||
public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames);
|
||||
|
||||
[NotNull]
|
||||
public override IVocabulary Vocabulary
|
||||
{
|
||||
get
|
||||
{
|
||||
return DefaultVocabulary;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GrammarFileName { get { return "InkBlotAntlrGrammar.g4"; } }
|
||||
|
||||
public override string[] RuleNames { get { return ruleNames; } }
|
||||
|
||||
public override int[] SerializedAtn { get { return _serializedATN; } }
|
||||
|
||||
static InkBlotAntlrGrammarParser() {
|
||||
decisionToDFA = new DFA[_ATN.NumberOfDecisions];
|
||||
for (int i = 0; i < _ATN.NumberOfDecisions; i++) {
|
||||
decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
public InkBlotAntlrGrammarParser(ITokenStream input) : this(input, Console.Out, Console.Error) { }
|
||||
|
||||
public InkBlotAntlrGrammarParser(ITokenStream input, TextWriter output, TextWriter errorOutput)
|
||||
: base(input, output, errorOutput)
|
||||
{
|
||||
Interpreter = new ParserATNSimulator(this, _ATN, decisionToDFA, sharedContextCache);
|
||||
}
|
||||
|
||||
public partial class StoryContext : ParserRuleContext {
|
||||
[System.Diagnostics.DebuggerNonUserCode] public ContentTextContext[] contentText() {
|
||||
return GetRuleContexts<ContentTextContext>();
|
||||
}
|
||||
[System.Diagnostics.DebuggerNonUserCode] public ContentTextContext contentText(int i) {
|
||||
return GetRuleContext<ContentTextContext>(i);
|
||||
}
|
||||
public StoryContext(ParserRuleContext parent, int invokingState)
|
||||
: base(parent, invokingState)
|
||||
{
|
||||
}
|
||||
public override int RuleIndex { get { return RULE_story; } }
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public override void EnterRule(IParseTreeListener listener) {
|
||||
IInkBlotAntlrGrammarListener typedListener = listener as IInkBlotAntlrGrammarListener;
|
||||
if (typedListener != null) typedListener.EnterStory(this);
|
||||
}
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public override void ExitRule(IParseTreeListener listener) {
|
||||
IInkBlotAntlrGrammarListener typedListener = listener as IInkBlotAntlrGrammarListener;
|
||||
if (typedListener != null) typedListener.ExitStory(this);
|
||||
}
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public override TResult Accept<TResult>(IParseTreeVisitor<TResult> visitor) {
|
||||
IInkBlotAntlrGrammarVisitor<TResult> typedVisitor = visitor as IInkBlotAntlrGrammarVisitor<TResult>;
|
||||
if (typedVisitor != null) return typedVisitor.VisitStory(this);
|
||||
else return visitor.VisitChildren(this);
|
||||
}
|
||||
}
|
||||
|
||||
[RuleVersion(0)]
|
||||
public StoryContext story() {
|
||||
StoryContext _localctx = new StoryContext(Context, State);
|
||||
EnterRule(_localctx, 0, RULE_story);
|
||||
int _la;
|
||||
try {
|
||||
EnterOuterAlt(_localctx, 1);
|
||||
{
|
||||
State = 5;
|
||||
ErrorHandler.Sync(this);
|
||||
_la = TokenStream.LA(1);
|
||||
do {
|
||||
{
|
||||
{
|
||||
State = 4;
|
||||
contentText();
|
||||
}
|
||||
}
|
||||
State = 7;
|
||||
ErrorHandler.Sync(this);
|
||||
_la = TokenStream.LA(1);
|
||||
} while ( _la==CONTENT_TEXT_NO_ESCAPE_SIMPLE );
|
||||
}
|
||||
}
|
||||
catch (RecognitionException re) {
|
||||
_localctx.exception = re;
|
||||
ErrorHandler.ReportError(this, re);
|
||||
ErrorHandler.Recover(this, re);
|
||||
}
|
||||
finally {
|
||||
ExitRule();
|
||||
}
|
||||
return _localctx;
|
||||
}
|
||||
|
||||
public partial class ContentTextContext : ParserRuleContext {
|
||||
[System.Diagnostics.DebuggerNonUserCode] public ITerminalNode CONTENT_TEXT_NO_ESCAPE_SIMPLE() { return GetToken(InkBlotAntlrGrammarParser.CONTENT_TEXT_NO_ESCAPE_SIMPLE, 0); }
|
||||
public ContentTextContext(ParserRuleContext parent, int invokingState)
|
||||
: base(parent, invokingState)
|
||||
{
|
||||
}
|
||||
public override int RuleIndex { get { return RULE_contentText; } }
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public override void EnterRule(IParseTreeListener listener) {
|
||||
IInkBlotAntlrGrammarListener typedListener = listener as IInkBlotAntlrGrammarListener;
|
||||
if (typedListener != null) typedListener.EnterContentText(this);
|
||||
}
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public override void ExitRule(IParseTreeListener listener) {
|
||||
IInkBlotAntlrGrammarListener typedListener = listener as IInkBlotAntlrGrammarListener;
|
||||
if (typedListener != null) typedListener.ExitContentText(this);
|
||||
}
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public override TResult Accept<TResult>(IParseTreeVisitor<TResult> visitor) {
|
||||
IInkBlotAntlrGrammarVisitor<TResult> typedVisitor = visitor as IInkBlotAntlrGrammarVisitor<TResult>;
|
||||
if (typedVisitor != null) return typedVisitor.VisitContentText(this);
|
||||
else return visitor.VisitChildren(this);
|
||||
}
|
||||
}
|
||||
|
||||
[RuleVersion(0)]
|
||||
public ContentTextContext contentText() {
|
||||
ContentTextContext _localctx = new ContentTextContext(Context, State);
|
||||
EnterRule(_localctx, 2, RULE_contentText);
|
||||
try {
|
||||
EnterOuterAlt(_localctx, 1);
|
||||
{
|
||||
State = 9;
|
||||
Match(CONTENT_TEXT_NO_ESCAPE_SIMPLE);
|
||||
}
|
||||
}
|
||||
catch (RecognitionException re) {
|
||||
_localctx.exception = re;
|
||||
ErrorHandler.ReportError(this, re);
|
||||
ErrorHandler.Recover(this, re);
|
||||
}
|
||||
finally {
|
||||
ExitRule();
|
||||
}
|
||||
return _localctx;
|
||||
}
|
||||
|
||||
private static int[] _serializedATN = {
|
||||
4,1,2,12,2,0,7,0,2,1,7,1,1,0,4,0,6,8,0,11,0,12,0,7,1,1,1,1,1,1,0,0,2,0,
|
||||
2,0,0,10,0,5,1,0,0,0,2,9,1,0,0,0,4,6,3,2,1,0,5,4,1,0,0,0,6,7,1,0,0,0,7,
|
||||
5,1,0,0,0,7,8,1,0,0,0,8,1,1,0,0,0,9,10,5,2,0,0,10,3,1,0,0,0,1,7
|
||||
};
|
||||
|
||||
public static readonly ATN _ATN =
|
||||
new ATNDeserializer().Deserialize(_serializedATN);
|
||||
|
||||
|
||||
}
|
||||
46
InkBlot/Generated/InkBlotAntlrGrammarVisitor.cs
Normal file
46
InkBlot/Generated/InkBlotAntlrGrammarVisitor.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// ANTLR Version: 4.13.2
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Generated from E:/ProgettiUnity/InkAntlr/InkBlot/InkBlot/InkBlotAntlrGrammar.g4 by ANTLR 4.13.2
|
||||
|
||||
// Unreachable code detected
|
||||
#pragma warning disable 0162
|
||||
// The variable '...' is assigned but its value is never used
|
||||
#pragma warning disable 0219
|
||||
// Missing XML comment for publicly visible type or member '...'
|
||||
#pragma warning disable 1591
|
||||
// Ambiguous reference in cref attribute
|
||||
#pragma warning disable 419
|
||||
|
||||
using Antlr4.Runtime.Misc;
|
||||
using Antlr4.Runtime.Tree;
|
||||
using IToken = Antlr4.Runtime.IToken;
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines a complete generic visitor for a parse tree produced
|
||||
/// by <see cref="InkBlotAntlrGrammarParser"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="Result">The return type of the visit operation.</typeparam>
|
||||
[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
|
||||
[System.CLSCompliant(false)]
|
||||
public interface IInkBlotAntlrGrammarVisitor<Result> : IParseTreeVisitor<Result> {
|
||||
/// <summary>
|
||||
/// Visit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.story"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
/// <return>The visitor result.</return>
|
||||
Result VisitStory([NotNull] InkBlotAntlrGrammarParser.StoryContext context);
|
||||
/// <summary>
|
||||
/// Visit a parse tree produced by <see cref="InkBlotAntlrGrammarParser.contentText"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The parse tree.</param>
|
||||
/// <return>The visitor result.</return>
|
||||
Result VisitContentText([NotNull] InkBlotAntlrGrammarParser.ContentTextContext context);
|
||||
}
|
||||
17
InkBlot/IFileReader.cs
Normal file
17
InkBlot/IFileReader.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace InkBlot;
|
||||
|
||||
/// <summary>
|
||||
/// An object that can read files.
|
||||
/// </summary>
|
||||
public interface IFileReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the contents of a file.
|
||||
/// </summary>
|
||||
/// <param name="filename">
|
||||
/// Name of the file to read. The name is relative to the main ink file. The name is normalized
|
||||
/// (uses only single forward slashes between path elements).
|
||||
/// </param>
|
||||
/// <returns>A stream with the contents of the file.</returns>
|
||||
Stream GetContents(string filename);
|
||||
}
|
||||
@@ -6,4 +6,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Antlr4.Runtime.Standard" Version="4.13.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
7
InkBlot/InkBlotAntlrGrammar.g4
Normal file
7
InkBlot/InkBlotAntlrGrammar.g4
Normal file
@@ -0,0 +1,7 @@
|
||||
grammar InkBlotAntlrGrammar;
|
||||
|
||||
import InkBlotAntlrLexer;
|
||||
|
||||
story: contentText+ ;
|
||||
|
||||
contentText: CONTENT_TEXT_NO_ESCAPE_SIMPLE ;
|
||||
22
InkBlot/InkBlotAntlrLexer.g4
Normal file
22
InkBlot/InkBlotAntlrLexer.g4
Normal file
@@ -0,0 +1,22 @@
|
||||
lexer grammar InkBlotAntlrLexer;
|
||||
|
||||
Whitespace: [ \t]+ ;
|
||||
|
||||
// see InkParser_Content.cs, ContentTextNoEscape and ContentTextAllowingEcapeChar for the escape case
|
||||
// this works for the base case where we're not parsing a string, nor a choice
|
||||
CONTENT_TEXT_NO_ESCAPE_SIMPLE:
|
||||
(
|
||||
// any character is valid, except for:
|
||||
// - {} ==> identifies embedded logic
|
||||
// - | ==> text alternatives, is forbidden even in non-logic text for some reason
|
||||
// - \n\r ==> a new line of content
|
||||
// - # ==> a tag
|
||||
// - \, < and - with exceptions (see below)
|
||||
~[{}|\n\r\\#-<]
|
||||
// any character can be escaped
|
||||
| '\\' [\u0000-\uFFFF] // TODO: is there a better way to say "any character"?
|
||||
// accept a - only if not followed by a > (->, a divert)
|
||||
| '-' { InputStream.LA(1) != '>' }?
|
||||
// same for threads (<-) and glue (<>)
|
||||
| '<' { InputStream.LA(1) != '-' && InputStream.LA(1) != '>' }?
|
||||
)+ ;
|
||||
34
InkBlot/InkBlotParser.cs
Normal file
34
InkBlot/InkBlotParser.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Antlr4.Runtime;
|
||||
using Antlr4.Runtime.Atn;
|
||||
using Antlr4.Runtime.Tree;
|
||||
using InkBlot.ParseHierarchy;
|
||||
using InkBlot.Visitor;
|
||||
|
||||
namespace InkBlot;
|
||||
|
||||
public class InkBlotParser
|
||||
{
|
||||
public (Story, IEnumerable<Diagnostic>) Parse(IFileReader fileReader, string mainFileName)
|
||||
{
|
||||
var stream = fileReader.GetContents(mainFileName);
|
||||
var inputStream = new AntlrInputStream(stream);
|
||||
var lexer = new InkBlotAntlrGrammarLexer(inputStream);
|
||||
var lexerErrorListener = new LexerErrorListener();
|
||||
lexer.RemoveErrorListeners();
|
||||
lexer.AddErrorListener(lexerErrorListener);
|
||||
var tokens = new CommonTokenStream(lexer);
|
||||
var parser = new InkBlotAntlrGrammarParser(tokens);
|
||||
var parserErrorListener = new ParserErrorListener();
|
||||
parser.RemoveErrorListeners();
|
||||
parser.AddErrorListener(parserErrorListener);
|
||||
#if DEBUG
|
||||
parser.AddErrorListener(new DiagnosticErrorListener());
|
||||
parser.Interpreter.PredictionMode = PredictionMode.LL_EXACT_AMBIG_DETECTION;
|
||||
#endif
|
||||
var tree = parser.story();
|
||||
var listener = new Listener();
|
||||
var walker = new ParseTreeWalker();
|
||||
walker.Walk(listener, tree);
|
||||
return (listener.Story, lexerErrorListener.Diagnostics.Concat(parserErrorListener.Diagnostics));
|
||||
}
|
||||
}
|
||||
16
InkBlot/LexerErrorListener.cs
Normal file
16
InkBlot/LexerErrorListener.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Antlr4.Runtime;
|
||||
|
||||
namespace InkBlot;
|
||||
|
||||
// from https://github.com/YarnSpinnerTool/YarnSpinner/blob/main/YarnSpinner.Compiler/ErrorListener.cs#L247
|
||||
internal sealed class LexerErrorListener : IAntlrErrorListener<int>
|
||||
{
|
||||
public readonly List<Diagnostic> Diagnostics = [];
|
||||
|
||||
public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line,
|
||||
int charPositionInLine, string msg, RecognitionException e)
|
||||
{
|
||||
var range = new Range(line - 1, charPositionInLine, line - 1, charPositionInLine + 1);
|
||||
Diagnostics.Add(new Diagnostic("", range, msg, ""));
|
||||
}
|
||||
}
|
||||
3
InkBlot/ParseHierarchy/Content.cs
Normal file
3
InkBlot/ParseHierarchy/Content.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace InkBlot.ParseHierarchy;
|
||||
|
||||
public record Content(string Text) : StoryNode;
|
||||
3
InkBlot/ParseHierarchy/Story.cs
Normal file
3
InkBlot/ParseHierarchy/Story.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace InkBlot.ParseHierarchy;
|
||||
|
||||
public record Story(IEnumerable<StoryNode> StoryNodes) : StoryNode;
|
||||
6
InkBlot/ParseHierarchy/StoryNode.cs
Normal file
6
InkBlot/ParseHierarchy/StoryNode.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace InkBlot.ParseHierarchy;
|
||||
|
||||
/// <summary>
|
||||
/// Any node in the parsed story
|
||||
/// </summary>
|
||||
public record StoryNode;
|
||||
58
InkBlot/ParserErrorListener.cs
Normal file
58
InkBlot/ParserErrorListener.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Text;
|
||||
using Antlr4.Runtime;
|
||||
|
||||
namespace InkBlot;
|
||||
|
||||
internal sealed class ParserErrorListener : BaseErrorListener
|
||||
{
|
||||
public readonly List<Diagnostic> Diagnostics = [];
|
||||
|
||||
public override void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line,
|
||||
int charPositionInLine,
|
||||
string msg, RecognitionException e)
|
||||
{
|
||||
// adapted from https://github.com/YarnSpinnerTool/YarnSpinner/blob/84e17d04fdf4ac2f824ed1d4d277bb3a1dde73f6/YarnSpinner.Compiler/ErrorListener.cs#L266
|
||||
var range = new Range(line - 1, charPositionInLine, line - 1, charPositionInLine + 1);
|
||||
var context = "";
|
||||
|
||||
if (offendingSymbol.TokenSource != null)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
// the line with the error on it
|
||||
var input = offendingSymbol.TokenSource.InputStream.ToString();
|
||||
if (input != null)
|
||||
{
|
||||
var lines = input.Split('\n');
|
||||
var errorLine = lines[line - 1];
|
||||
builder.AppendLine(errorLine);
|
||||
|
||||
// adding indicator symbols pointing out where the error is
|
||||
// on the line
|
||||
var start = offendingSymbol.StartIndex;
|
||||
var stop = offendingSymbol.StopIndex;
|
||||
if (start >= 0 && stop >= 0)
|
||||
{
|
||||
// the end point of the error in "line space"
|
||||
var end = stop - start + charPositionInLine + 1;
|
||||
for (var i = 0; i < end; i++)
|
||||
// move over until we are at the point we need to
|
||||
// be
|
||||
if (i >= charPositionInLine && i < end)
|
||||
builder.Append('^');
|
||||
else
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
context = builder.ToString();
|
||||
|
||||
range = new Range(offendingSymbol.Line - 1, offendingSymbol.Column, offendingSymbol.Line - 1,
|
||||
offendingSymbol.Column + offendingSymbol.Text.Length);
|
||||
}
|
||||
}
|
||||
|
||||
var diagnostic = new Diagnostic("", range, msg, context);
|
||||
|
||||
Diagnostics.Add(diagnostic);
|
||||
}
|
||||
}
|
||||
5
InkBlot/Visitor/Listener.cs
Normal file
5
InkBlot/Visitor/Listener.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace InkBlot.Visitor;
|
||||
|
||||
public partial class Listener : InkBlotAntlrGrammarBaseListener
|
||||
{
|
||||
}
|
||||
34
InkBlot/Visitor/ListenerContentText.cs
Normal file
34
InkBlot/Visitor/ListenerContentText.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Antlr4.Runtime.Tree;
|
||||
|
||||
namespace InkBlot.Visitor;
|
||||
|
||||
public partial class Listener
|
||||
{
|
||||
private readonly ParseTreeProperty<string> _contentTextValue = new();
|
||||
|
||||
private readonly Regex _escapeRegex = MyRegex();
|
||||
|
||||
[GeneratedRegex(@"\\(.)")]
|
||||
private static partial Regex MyRegex();
|
||||
|
||||
private string GetContentText(InkBlotAntlrGrammarParser.ContentTextContext context)
|
||||
{
|
||||
return _contentTextValue.Get(context);
|
||||
}
|
||||
|
||||
public override void ExitContentText(InkBlotAntlrGrammarParser.ContentTextContext context)
|
||||
{
|
||||
// escape sequences are captured by this node, but not interpreted
|
||||
var contentWithEscapes = context.CONTENT_TEXT_NO_ESCAPE_SIMPLE().ToString();
|
||||
if (contentWithEscapes == null)
|
||||
// when does this happen? :?
|
||||
throw new InvalidOperationException();
|
||||
|
||||
// replace \X with just X
|
||||
var content = _escapeRegex.Replace(contentWithEscapes, match => match.Groups[1].Value);
|
||||
|
||||
// save the result
|
||||
_contentTextValue.Put(context, content);
|
||||
}
|
||||
}
|
||||
20
InkBlot/Visitor/ListenerStory.cs
Normal file
20
InkBlot/Visitor/ListenerStory.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using InkBlot.ParseHierarchy;
|
||||
|
||||
namespace InkBlot.Visitor;
|
||||
|
||||
public partial class Listener
|
||||
{
|
||||
private Story? _story;
|
||||
|
||||
public Story Story => _story ?? throw new InvalidOperationException("No story found yet.");
|
||||
|
||||
public override void ExitStory(InkBlotAntlrGrammarParser.StoryContext context)
|
||||
{
|
||||
var storyNodes = context.children.Select(child => child switch
|
||||
{
|
||||
InkBlotAntlrGrammarParser.ContentTextContext contentText => new Content(GetContentText(contentText)),
|
||||
_ => throw new InvalidOperationException($"unknown context of type {child.GetType()}")
|
||||
});
|
||||
_story = new Story(storyNodes);
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InkBlot\InkBlot.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
Console.WriteLine("Hello, World!");
|
||||
using System.Text;
|
||||
using InkBlot;
|
||||
|
||||
var parser = new InkBlotParser();
|
||||
parser.Parse(new PreMadeFileReader(new Dictionary<string, string>
|
||||
{
|
||||
{ "main.ink", "Hel-\\lo!" }
|
||||
}), "main.ink");
|
||||
|
||||
|
||||
internal class PreMadeFileReader(Dictionary<string, string> filesToContents) : IFileReader
|
||||
{
|
||||
public Stream GetContents(string filename)
|
||||
{
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(filesToContents[filename]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user