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

226 lines
7.2 KiB
C#

using Ink.Parsed;
using System.Diagnostics;
namespace Ink
{
public partial class InkParser
{
protected Choice Choice()
{
bool onceOnlyChoice = true;
var bullets = Interleave <string>(OptionalExclude(Whitespace), String("*") );
if (bullets == null) {
bullets = Interleave <string>(OptionalExclude(Whitespace), String("+") );
if (bullets == null) {
return null;
}
onceOnlyChoice = false;
}
// Optional name for the choice
Identifier optionalName = Parse(BracketedName);
Whitespace ();
// Optional condition for whether the choice should be shown to the player
Expression conditionExpr = Parse(ChoiceCondition);
Whitespace ();
// Ordinarily we avoid parser state variables like these, since
// nesting would require us to store them in a stack. But since you should
// never be able to nest choices within choice content, it's fine here.
Debug.Assert(_parsingChoice == false, "Already parsing a choice - shouldn't have nested choices");
_parsingChoice = true;
ContentList startContent = null;
var startTextAndLogic = Parse (MixedTextAndLogic);
if (startTextAndLogic != null)
startContent = new ContentList (startTextAndLogic);
ContentList optionOnlyContent = null;
ContentList innerContent = null;
// Check for a the weave style format:
// * "Hello[."]," he said.
bool hasWeaveStyleInlineBrackets = ParseString("[") != null;
if (hasWeaveStyleInlineBrackets) {
var optionOnlyTextAndLogic = Parse (MixedTextAndLogic);
if (optionOnlyTextAndLogic != null)
optionOnlyContent = new ContentList (optionOnlyTextAndLogic);
Expect (String("]"), "closing ']' for weave-style option");
var innerTextAndLogic = Parse (MixedTextAndLogic);
if( innerTextAndLogic != null )
innerContent = new ContentList (innerTextAndLogic);
}
Whitespace ();
// Finally, now we know we're at the end of the main choice body, parse
// any diverts separately.
var diverts = Parse(MultiDivert);
_parsingChoice = false;
Whitespace ();
// Completely empty choice without even an empty divert?
bool emptyContent = !startContent && !innerContent && !optionOnlyContent;
if (emptyContent && diverts == null)
Warning ("Choice is completely empty. Interpretting as a default fallback choice. Add a divert arrow to remove this warning: * ->");
// * [] some text
else if (!startContent && hasWeaveStyleInlineBrackets && !optionOnlyContent)
Warning ("Blank choice - if you intended a default fallback choice, use the `* ->` syntax");
if (!innerContent) innerContent = new ContentList ();
var tags = Parse (Tags);
if (tags != null) {
innerContent.AddContent(tags);
}
// Normal diverts on the end of a choice - simply add to the normal content
if (diverts != null) {
foreach (var divObj in diverts) {
// may be TunnelOnwards
var div = divObj as Divert;
// Empty divert serves no purpose other than to say
// "this choice is intentionally left blank"
// (as an invisible default choice)
if (div && div.isEmpty) continue;
innerContent.AddContent (divObj);
}
}
// Terminate main content with a newline since this is the end of the line
// Note that this will be redundant if the diverts above definitely take
// the flow away permanently.
innerContent.AddContent (new Text ("\n"));
var choice = new Choice (startContent, optionOnlyContent, innerContent);
choice.identifier = optionalName;
choice.indentationDepth = bullets.Count;
choice.hasWeaveStyleInlineBrackets = hasWeaveStyleInlineBrackets;
choice.condition = conditionExpr;
choice.onceOnly = onceOnlyChoice;
choice.isInvisibleDefault = emptyContent;
return choice;
}
protected Expression ChoiceCondition()
{
var conditions = Interleave<Expression> (ChoiceSingleCondition, ChoiceConditionsSpace);
if (conditions == null)
return null;
else if (conditions.Count == 1)
return conditions [0];
else {
return new MultipleConditionExpression (conditions);
}
}
protected object ChoiceConditionsSpace()
{
// Both optional
// Newline includes initial end of line whitespace
Newline ();
Whitespace ();
return ParseSuccess;
}
protected Expression ChoiceSingleCondition()
{
if (ParseString ("{") == null)
return null;
var condExpr = Expect(Expression, "choice condition inside { }") as Expression;
DisallowIncrement (condExpr);
Expect (String ("}"), "closing '}' for choice condition");
return condExpr;
}
protected Gather Gather()
{
object gatherDashCountObj = Parse(GatherDashes);
if (gatherDashCountObj == null) {
return null;
}
int gatherDashCount = (int)gatherDashCountObj;
// Optional name for the gather
Identifier optionalName = Parse(BracketedName);
var gather = new Gather (optionalName, gatherDashCount);
// Optional newline before gather's content begins
Newline ();
return gather;
}
protected object GatherDashes()
{
Whitespace ();
int gatherDashCount = 0;
while (ParseDashNotArrow () != null) {
gatherDashCount++;
Whitespace ();
}
if (gatherDashCount == 0)
return null;
return gatherDashCount;
}
protected object ParseDashNotArrow()
{
var ruleId = BeginRule ();
if (ParseString ("->") == null && ParseSingleCharacter () == '-') {
return SucceedRule (ruleId);
} else {
return FailRule (ruleId);
}
}
protected Identifier BracketedName()
{
if (ParseString ("(") == null)
return null;
Whitespace ();
Identifier name = Parse(IdentifierWithMetadata);
if (name == null)
return null;
Whitespace ();
Expect (String (")"), "closing ')' for bracketed name");
return name;
}
bool _parsingChoice;
}
}