Files
Mementos/Unity/Alternate Genre Jam/Assets/Ink/InkLibs/InkCompiler/InkParser/InkParser_Knot.cs
2021-06-30 21:39:19 +10:00

240 lines
7.5 KiB
C#

using System.Collections.Generic;
using Ink.Parsed;
using System.Linq;
namespace Ink
{
public partial class InkParser
{
protected class NameWithMetadata {
public string name;
public Runtime.DebugMetadata metadata;
}
protected class FlowDecl
{
public Identifier name;
public List<FlowBase.Argument> arguments;
public bool isFunction;
}
protected Knot KnotDefinition()
{
var knotDecl = Parse(KnotDeclaration);
if (knotDecl == null)
return null;
Expect(EndOfLine, "end of line after knot name definition", recoveryRule: SkipToNextLine);
ParseRule innerKnotStatements = () => StatementsAtLevel (StatementLevel.Knot);
var content = Expect (innerKnotStatements, "at least one line within the knot", recoveryRule: KnotStitchNoContentRecoveryRule) as List<Parsed.Object>;
return new Knot (knotDecl.name, content, knotDecl.arguments, knotDecl.isFunction);
}
protected FlowDecl KnotDeclaration()
{
Whitespace ();
if (KnotTitleEquals () == null)
return null;
Whitespace ();
Identifier identifier = Parse(IdentifierWithMetadata);
Identifier knotName;
bool isFunc = identifier?.name == "function";
if (isFunc) {
Expect (Whitespace, "whitespace after the 'function' keyword");
knotName = Parse(IdentifierWithMetadata);
} else {
knotName = identifier;
}
if (knotName == null) {
Error ("Expected the name of the " + (isFunc ? "function" : "knot"));
knotName = new Identifier { name = "" }; // prevent later null ref
}
Whitespace ();
List<FlowBase.Argument> parameterNames = Parse (BracketedKnotDeclArguments);
Whitespace ();
// Optional equals after name
Parse(KnotTitleEquals);
return new FlowDecl () { name = knotName, arguments = parameterNames, isFunction = isFunc };
}
protected string KnotTitleEquals()
{
// 2+ "=" starts a knot
var multiEquals = ParseCharactersFromString ("=");
if (multiEquals == null || multiEquals.Length <= 1) {
return null;
} else {
return multiEquals;
}
}
protected object StitchDefinition()
{
var decl = Parse(StitchDeclaration);
if (decl == null)
return null;
Expect(EndOfLine, "end of line after stitch name", recoveryRule: SkipToNextLine);
ParseRule innerStitchStatements = () => StatementsAtLevel (StatementLevel.Stitch);
var content = Expect(innerStitchStatements, "at least one line within the stitch", recoveryRule: KnotStitchNoContentRecoveryRule) as List<Parsed.Object>;
return new Stitch (decl.name, content, decl.arguments, decl.isFunction );
}
protected FlowDecl StitchDeclaration()
{
Whitespace ();
// Single "=" to define a stitch
if (ParseString ("=") == null)
return null;
// If there's more than one "=", that's actually a knot definition (or divert), so this rule should fail
if (ParseString ("=") != null)
return null;
Whitespace ();
// Stitches aren't allowed to be functions, but we parse it anyway and report the error later
bool isFunc = ParseString ("function") != null;
if ( isFunc ) {
Whitespace ();
}
Identifier stitchName = Parse(IdentifierWithMetadata);
if (stitchName == null)
return null;
Whitespace ();
List<FlowBase.Argument> flowArgs = Parse(BracketedKnotDeclArguments);
Whitespace ();
return new FlowDecl () { name = stitchName, arguments = flowArgs, isFunction = isFunc };
}
protected object KnotStitchNoContentRecoveryRule()
{
// Jump ahead to the next knot or the end of the file
ParseUntil (KnotDeclaration, new CharacterSet ("="), null);
var recoveredFlowContent = new List<Parsed.Object>();
recoveredFlowContent.Add( new Parsed.Text("<ERROR IN FLOW>" ) );
return recoveredFlowContent;
}
protected List<FlowBase.Argument> BracketedKnotDeclArguments()
{
if (ParseString ("(") == null)
return null;
var flowArguments = Interleave<FlowBase.Argument>(Spaced(FlowDeclArgument), Exclude (String(",")));
Expect (String (")"), "closing ')' for parameter list");
// If no parameters, create an empty list so that this method is type safe and
// doesn't attempt to return the ParseSuccess object
if (flowArguments == null) {
flowArguments = new List<FlowBase.Argument> ();
}
return flowArguments;
}
protected FlowBase.Argument FlowDeclArgument()
{
// Possible forms:
// name
// -> name (variable divert target argument
// ref name
// ref -> name (variable divert target by reference)
var firstIden = Parse(IdentifierWithMetadata);
Whitespace ();
var divertArrow = ParseDivertArrow ();
Whitespace ();
var secondIden = Parse(IdentifierWithMetadata);
if (firstIden == null && secondIden == null)
return null;
var flowArg = new FlowBase.Argument ();
if (divertArrow != null) {
flowArg.isDivertTarget = true;
}
// Passing by reference
if (firstIden != null && firstIden.name == "ref") {
if (secondIden == null) {
Error ("Expected an parameter name after 'ref'");
}
flowArg.identifier = secondIden;
flowArg.isByReference = true;
}
// Simple argument name
else {
if (flowArg.isDivertTarget) {
flowArg.identifier = secondIden;
} else {
flowArg.identifier = firstIden;
}
if (flowArg.identifier == null) {
Error ("Expected an parameter name");
}
flowArg.isByReference = false;
}
return flowArg;
}
protected ExternalDeclaration ExternalDeclaration()
{
Whitespace ();
Identifier external = Parse(IdentifierWithMetadata);
if (external == null || external.name != "EXTERNAL")
return null;
Whitespace ();
var funcIdentifier = Expect(IdentifierWithMetadata, "name of external function") as Identifier ?? new Identifier();
Whitespace ();
var parameterNames = Expect (BracketedKnotDeclArguments, "declaration of arguments for EXTERNAL, even if empty, i.e. 'EXTERNAL "+funcIdentifier+"()'") as List<FlowBase.Argument>;
if (parameterNames == null)
parameterNames = new List<FlowBase.Argument> ();
var argNames = parameterNames.Select (arg => arg.identifier?.name).ToList();
return new ExternalDeclaration (funcIdentifier, argNames);
}
}
}