using System; using Amphora; using System.Collections.Generic; using System.Linq; namespace Ariadne.UnitTests { /// /// ArcTests.TutorialArc written as a script: an intro pause, then passages Act2 and Act3, which both have a /// "connect" step. Log records run-once code. /// public partial class TutorialScript : Arc { [Scratch] public readonly List Log = new List(); private void Act2(ArcTests.Gates gates) { Log.Add("act2 start"); build: while (!gates.Open.Contains("build")) { Yield(); } Log.Add("built"); connect: while (!gates.Open.Contains("connect")) { Yield(); } } private void Act3(ArcTests.Gates gates) { Log.Add("act3 start"); connect: while (!gates.Open.Contains("act3 connect")) { Yield(); } } protected override void Run(ArcTests.Gates gates) { Log.Add("start"); intro: while (!gates.Open.Contains("intro")) { Yield(); } Log.Add("after intro"); Act2(gates); Act3(gates); Log.Add("end"); } } public partial class RepeatedPassageScript : Arc { private void Briefing(ArcTests.Gates gates) { read: while (!gates.Open.Contains("read")) { Yield(); } gates.Open.Remove("read"); } protected override void Run(ArcTests.Gates gates) { first: Briefing(gates); second: Briefing(gates); } } public partial class TwoPausesInOneStepScript : Arc { protected override void Run(ArcTests.Gates gates) { connect: while (!gates.Open.Contains("breaker")) { Yield(); } while (!gates.Open.Contains("load")) { Yield(); } } } /// /// Calls a two-pause passage twice from one call site, so the second call can begin in the tick the first ends. /// public partial class LoopedPassageScript : Arc { [Scratch] public readonly List Log = new List(); private void Step(ArcTests.Gates gates) { Log.Add("step begins"); first: while (!gates.Open.Contains("A")) { Yield(); } Log.Add("A seen"); second: while (!gates.Open.Contains("B")) { Yield(); } Log.Add("step ends"); } protected override void Run(ArcTests.Gates gates) { for (int i = 0; i < 2; i++) { Step(gates); } } } /// /// Calls a passage twice, which can return before its pause the second time. /// public partial class LoopedEarlyReturnScript : Arc { [Scratch] public readonly List Log = new List(); private void Step(ArcTests.Gates gates) { Log.Add("step begins"); if (gates.Open.Contains("skip")) { return; } wait: while (!gates.Open.Contains("A")) { Yield(); } Log.Add("step ends"); } protected override void Run(ArcTests.Gates gates) { for (int i = 0; i < 2; i++) { Step(gates); } } } public partial class EarlyReturnScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("skip")) { return; } wait: while (!gates.Open.Contains("wait")) { Yield(); } Log.Add("waited"); } } /// /// Counts the ticks spent waiting in a local, then hands a crate count to a passage that counts it down. /// public partial class LocalsScript : Arc { [Scratch] public readonly List Log = new List(); [Scratch] public int ArgumentEvaluations; private int Counted(int value) { ArgumentEvaluations++; return value; } private void Deliver(ArcTests.Gates gates, int crates) { deliver: while (crates > 0) { if (gates.Open.Remove("crate")) { crates--; } Yield(); } Log.Add("delivered"); } protected override void Run(ArcTests.Gates gates) { string greeting = "hello"; Log.Add(greeting); int waits = 0; wait: while (!gates.Open.Contains("go")) { waits++; Yield(); } Log.Add("waited " + waits); Deliver(gates, Counted(2)); } } public partial class CapturedLocalScript : Arc { protected override void Run(ArcTests.Gates gates) { string wanted = "go"; wait: while (!gates.Open.Any(gate => gate == wanted)) { Yield(); } } } public struct Point { public double X; public double Y; public Point(double x, double y) { X = x; Y = y; } } public class Waypoint { public string Name; public Point At; } /// /// Keeps a struct in a local across a pause, a class in a field, and a list behind an interface. /// public partial class UserTypesScript : Arc { public Waypoint Destination; public IList Visits = new List(); protected override void Run(ArcTests.Gates gates) { var start = new Point(1.5, -2); wait: while (!gates.Open.Contains("go")) { Yield(); } Destination = new Waypoint { Name = "back to start", At = start }; } } /// /// A game's base class for its scripts, with state of its own to save. /// public abstract partial class MissionArc : Arc { public int Reward = 5; public string Title { get; set; } = "untitled"; [Scratch] public int Cache = 1; } public partial class MissionScript : MissionArc { public int Stage; protected override void Run(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("go")) { Stage++; Yield(); } } } public partial class ThrowingScript : Arc { protected override void Run(ArcTests.Gates gates) { explode: while (!gates.Open.Contains("never")) { if (gates.Open.Contains("explode")) { throw new InvalidOperationException("boom"); } Yield(); } } } /// /// Pauses at the top level, then inside each branch of an else-if chain. /// public partial class BranchingScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { Log.Add("start"); first: Yield(); Log.Add("after first"); if (gates.Open.Contains("left")) { Log.Add("left"); second: Yield(); Log.Add("left done"); } else if (gates.Open.Contains("middle")) { Log.Add("middle"); third: Yield(); Log.Add("middle done"); } else { Log.Add("right"); fourth: Yield(); Log.Add("right done"); } Log.Add("end"); } } /// /// Pauses inside an if and a plain block nested in it, with a pause after both. /// public partial class NestedBlocksScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("outer")) { Log.Add("outer"); { Log.Add("inner"); deep: Yield(); Log.Add("inner done"); } Log.Add("outer done"); } after: Yield(); Log.Add("end"); } } /// /// Counts how often its condition runs, which a resume inside the if must not add to. /// public partial class ConditionProbeScript : Arc { [Scratch] public readonly List Log = new List(); [Scratch] public int Probes; private bool Probe(ArcTests.Gates gates) { Probes++; return gates.Open.Contains("go"); } protected override void Run(ArcTests.Gates gates) { if (Probe(gates)) { inside: Yield(); Log.Add("inside"); } Log.Add("end"); } } /// /// Declares a local called total in each branch of an if, and keeps it across a pause. /// public partial class BranchLocalsScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("big")) { int total = 10; big: Yield(); total++; Log.Add("big " + total); } else { int total = 1; small: Yield(); total++; Log.Add("small " + total); } } } /// /// Assigns a local after a pause that it was declared before, without ever reading it across the pause. /// public partial class AcrossScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("go")) { int doubled; mid: Yield(); doubled = gates.Open.Count * 2; Log.Add("doubled " + doubled); } } } /// /// Calls a passage from one branch and waits in a loop in the other. /// public partial class NestedKindsScript : Arc { [Scratch] public readonly List Log = new List(); private void Briefing(ArcTests.Gates gates) { read: while (!gates.Open.Contains("read")) { Yield(); } Log.Add("briefed"); } protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("brief")) { Briefing(gates); Log.Add("after brief"); } else { wait: while (!gates.Open.Contains("go")) { Yield(); } Log.Add("went"); } } } /// /// Changes a local declared at the top of the passage on both sides of a pause inside an if. /// public partial class SharedCounterScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int visits = 0; if (gates.Open.Contains("go")) { visits++; step: Yield(); visits++; } Log.Add("visits " + visits); } } /// /// Pauses in the middle of a while loop that can also continue and break before reaching the pause. /// public partial class WhileMiddleScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int round = 0; wait: while (round < 4) { round++; if (gates.Open.Contains("skip" + round)) { continue; } if (gates.Open.Contains("stop" + round)) { break; } Log.Add("before " + round); Yield(); Log.Add("after " + round); } Log.Add("done " + round); } } /// /// Pauses at the start of each trip around a for loop, whose counter has to survive a save. /// public partial class ForPauseScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { wait: for (int i = 0; i < 3; i++) { Log.Add("i " + i); Yield(); } Log.Add("end"); } } public partial class DoPauseScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int count = 0; wait: do { Log.Add("do " + count); count++; Yield(); } while (count < 3); Log.Add("done"); } } /// /// Waits in a loop inside a for loop, so the pause sits two loops deep. /// public partial class NestedLoopsScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { row: for (int j = 0; j < 2; j++) { int k = 0; while (k < 2) { Log.Add("j" + j + "k" + k); k++; Yield(); } Log.Add("row " + j); } } } /// /// Declares a local in a loop body and uses it after the pause, with a continue inside a switch. /// public partial class LoopBodyLocalsScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int count = 0; wait: while (count < 3) { count++; switch (count) { case 2: continue; } int seen = gates.Open.Count; Yield(); Log.Add("count " + count + " seen " + seen); } Log.Add("end"); } } /// /// Continues past one trip of a for loop, which has to run the loop's step before the next test. /// public partial class ForContinueScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { wait: for (int i = 0; i < 4; i++) { if (i == 1) { continue; } Log.Add("i " + i); Yield(); } Log.Add("end"); } } /// /// Continues past one trip of a do loop, which has to test the loop's condition before the next trip. /// public partial class DoContinueScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int n = 0; wait: do { n++; if (n == 2) { continue; } Log.Add("n " + n); Yield(); } while (n < 3); Log.Add("end"); } } /// /// Pauses in some sections of a switch and not in others, with two labels on one of them. /// public partial class SwitchPauseScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int choice = 0; if (gates.Open.Contains("a")) { choice = 1; } else if (gates.Open.Contains("b")) { choice = 2; } switch (choice) { case 1: Log.Add("one"); first: Yield(); Log.Add("one done"); break; case 2: case 3: Log.Add("two or three"); second: Yield(); Log.Add("two or three done"); break; default: Log.Add("other"); break; } Log.Add("end"); } } /// /// Continues from one section of a switch inside a loop and pauses in another. /// public partial class SwitchLoopScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int n = 0; wait: while (n < 3) { n++; switch (n) { case 2: continue; case 3: Log.Add("three"); Yield(); break; default: Log.Add("other " + n); break; } Log.Add("after " + n); } Log.Add("end"); } } public partial class GotoCaseScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { int state = gates.Open.Count + 1; switch (state) { case 1: Log.Add("one"); goto case 2; case 2: Log.Add("two"); step: Yield(); Log.Add("after"); break; } } } /// /// Parses a number in a guard clause and keeps the out variable across a pause. /// public partial class GuardClauseScript : Arc { [Scratch] public readonly List Log = new List(); [Scratch] public string Text = "5"; protected override void Run(ArcTests.Gates gates) { if (!int.TryParse(Text, out int count)) { Log.Add("bad"); return; } wait: Yield(); Log.Add("count " + count); } } /// /// Keeps a pattern variable from an if's condition across a pause inside the if. /// public partial class PatternIfScript : Arc { [Scratch] public readonly List Log = new List(); [Scratch] public object Thing; protected override void Run(ArcTests.Gates gates) { if (Thing is string text && text.Length > 0) { wait: Yield(); Log.Add("text " + text); } else { Log.Add("none"); } } } /// /// Takes an item from a loop condition's out variable and keeps it across a pause in the body. /// public partial class OutLoopScript : Arc { [Scratch] public readonly List Log = new List(); public int Remaining = 2; private bool Next(out int item) { item = Remaining; return Remaining-- > 0; } protected override void Run(ArcTests.Gates gates) { wait: while (Next(out int item)) { Yield(); Log.Add("item " + item); } Log.Add("end"); } } /// /// Picks a section with a pattern variable in the case label and keeps it across a pause in the section. /// public partial class PatternCaseScript : Arc { [Scratch] public readonly List Log = new List(); [Scratch] public object Thing; protected override void Run(ArcTests.Gates gates) { switch (Thing) { case string text when text.Length > 0: Log.Add("string " + text); found: Yield(); Log.Add("done " + text); break; case int number: Log.Add("int " + number); break; default: Log.Add("other"); break; } } } /// /// Deconstructs a tuple into locals that are still needed after a pause. /// public partial class DeconstructionScript : Arc { [Scratch] public readonly List Log = new List(); [Scratch] public (int, int) Bounds = (2, 9); protected override void Run(ArcTests.Gates gates) { var (low, high) = Bounds; var (spare, (inner, _)) = (low + 1, (high + 1, 0)); wait: Yield(); Log.Add(low + "-" + high + " " + inner); } } /// /// Waits in a passage that returns a number, then logs the number it was handed. /// public partial class AskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { first: int choice = Ask(gates); Log.Add("choice " + choice); } } /// /// Hands a value up through two passages. /// public partial class ValueChainScript : Arc { [Scratch] public readonly List Log = new List(); private int Inner(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("inner")) { Yield(); } return 7; } private int Outer(ArcTests.Gates gates) { return Inner(gates); } protected override void Run(ArcTests.Gates gates) { int result = Outer(gates); Log.Add("result " + result); } } /// /// Keeps the value a passage returned across a later pause. /// public partial class KeptResultScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { int first = Ask(gates); second: Yield(); Log.Add("first " + first); } } /// /// Takes passage values into a field, then a discard, each keyed by its own label. /// public partial class AssignedValuesScript : Arc { [Scratch] public readonly List Log = new List(); public int Total; private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { one: Total = Ask(gates); two: _ = Ask(gates); Log.Add("total " + Total); } } /// /// Returns from different branches of a passage, one of which pauses first. /// public partial class BranchingValueScript : Arc { [Scratch] public readonly List Log = new List(); private string Pick(ArcTests.Gates gates) { if (gates.Open.Contains("left")) { wait: Yield(); return "left done"; } return "right"; } protected override void Run(ArcTests.Gates gates) { string picked = Pick(gates); Log.Add(picked); } } /// /// Reads a field before a call that pauses, so the field's old value is what gets added. /// public partial class SpillOrderScript : Arc { [Scratch] public readonly List Log = new List(); public int Offset = 100; private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { int result = Offset + Ask(gates); Log.Add("result " + result); } } /// /// Pauses in one call, then another, in a single expression. /// public partial class TwoCallsScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } private int AskLater(ArcTests.Gates gates) { later: while (!gates.Open.Contains("later")) { Yield(); } return 7; } protected override void Run(ArcTests.Gates gates) { int sum = Ask(gates) * 100 + AskLater(gates); Log.Add("sum " + sum); } } /// /// Returns more than a bare call, and uses a call as a receiver. /// public partial class SpillReturnScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } private int Doubled(ArcTests.Gates gates) { return Ask(gates) * 2; } protected override void Run(ArcTests.Gates gates) { int doubled = Doubled(gates); Log.Add("doubled " + doubled); } } public partial class SpillReceiverScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { Log.Add("got " + Ask(gates).ToString()); } } /// /// Hands a value call's result to a passage that pauses with it. /// public partial class RememberScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } private void Remember(ArcTests.Gates gates, int value) { wait: while (!gates.Open.Contains("remember")) { Yield(); } Log.Add("remembered " + value); } protected override void Run(ArcTests.Gates gates) { Remember(gates, Ask(gates)); } } /// /// Asks only when a flag is set, in the right side of an and. /// public partial class AndAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { bool ok = gates.Open.Contains("flag") && Ask(gates) > 15; Log.Add("ok " + ok); } } /// /// Asks only when a flag is not set, in the right side of an or. /// public partial class OrAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { bool ok = gates.Open.Contains("flag") || Ask(gates) > 15; Log.Add("ok " + ok); } } /// /// Uses a cached number if there is one, and asks if not. /// public partial class CoalesceAskScript : Arc { [Scratch] public readonly List Log = new List(); public int? Cached; private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { int value = Cached ?? Ask(gates); Log.Add("value " + value); } } /// /// Picks between a literal and a passage. /// public partial class TernaryAskScript : Arc { [Scratch] public readonly List Log = new List(); private string Describe(ArcTests.Gates gates) { describe: while (!gates.Open.Contains("describe")) { Yield(); } return "slow"; } protected override void Run(ArcTests.Gates gates) { string how = gates.Open.Contains("fast") ? "quick" : Describe(gates); Log.Add("how " + how); } } public class Recorder { public readonly List Seen = new List(); public void Record(int value) { Seen.Add(value); } } /// /// Calls a method on an object that may not be there, with an argument that pauses. /// public partial class MaybeRecordScript : Arc { public Recorder Helper; private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { Helper?.Record(Ask(gates)); } } /// /// Reads a field ahead of a conditional that might pause, so its old value is what gets used. /// public partial class OrderThroughBranchScript : Arc { [Scratch] public readonly List Log = new List(); public int Offset = 100; private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { int total = Offset + (gates.Open.Contains("flag") ? Ask(gates) : 0); Log.Add("total " + total); } } /// /// Branches on a passage call in an if condition. /// public partial class IfAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { if (Ask(gates) > 15) { Log.Add("big"); } else { Log.Add("small"); } } } /// /// Asks again on every trip of a while loop. /// public partial class WhileAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { int trips = 0; while (Ask(gates) < 25) { trips++; gates.Open.Add("trip" + trips); } Log.Add("trips " + trips); } } /// /// Asks after each trip of a do loop. /// public partial class DoAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { int trips = 0; do { trips++; gates.Open.Add("trip" + trips); } while (Ask(gates) < 25); Log.Add("trips " + trips); } } /// /// Starts a for loop at a value it asks for. /// public partial class ForInitAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { for (int i = Ask(gates); i < 24; i++) { Log.Add("i " + i); } } } /// /// Asks on every test of a for loop. /// public partial class ForConditionAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { for (int i = 0; i < Ask(gates) / 10; i++) { Log.Add("i " + i); } } } /// /// Asks in the step of a for loop. /// public partial class ForStepAskScript : Arc { [Scratch] public readonly List Log = new List(); private void Note(int value) { Log.Add("note " + value); } private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { for (int i = 0; i < 2; Note(Ask(gates))) { i++; } } } /// /// Switches on a passage call. /// public partial class SwitchAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { int waited = 0; wait: while (!gates.Open.Contains("answer")) { waited++; Yield(); } return gates.Open.Count * 10 + waited; } protected override void Run(ArcTests.Gates gates) { switch (Ask(gates)) { case 11: Log.Add("eleven"); break; default: Log.Add("other"); break; } } } /// /// Declares two locals at once, the second from a passage that pauses and the first. /// public partial class TwoDeclaratorsScript : Arc { [Scratch] public readonly List Log = new List(); private int Wait(ArcTests.Gates gates, string name) { wait: while (!gates.Open.Contains(name)) { Yield(); } return gates.Open.Count; } protected override void Run(ArcTests.Gates gates) { both: int a = Wait(gates, "one"), b = Wait(gates, "two") + a; Log.Add(a + "," + b); } } /// /// Declares a local from a plain call, then another from a passage. /// public partial class DeclaratorOrderScript : Arc { [Scratch] public readonly List Log = new List(); public int Calls; private int Next() { return ++Calls; } private int Wait(ArcTests.Gates gates, string name) { wait: while (!gates.Open.Contains(name)) { Yield(); } return gates.Open.Count; } protected override void Run(ArcTests.Gates gates) { int first = Next(), second = Wait(gates, "one"); Log.Add(first + "," + second + "," + Calls); } } /// /// Tests a passage's answer against patterns, and keeps the variable one declares across a later pause. /// public partial class PatternAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("answer")) { Yield(); } return gates.Open.Count * 10; } protected override void Run(ArcTests.Gates gates) { if (Ask(gates) is int n && n > 15) { show: Yield(); Log.Add("n " + n); } Log.Add("done"); } } /// /// Sorts a passage's answer with a switch expression and a relational pattern. /// public partial class SwitchExpressionAskScript : Arc { [Scratch] public readonly List Log = new List(); private int Ask(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("answer")) { Yield(); } return gates.Open.Count * 10; } protected override void Run(ArcTests.Gates gates) { string size = Ask(gates) switch { > 15 => "big", _ => "small" }; Log.Add(size); } } /// /// Picks what to do from a switch expression whose arms ask, one of them using a pattern variable. /// public partial class ArmAskScript : Arc { [Scratch] public readonly List Log = new List(); public int Calls; private int Ask(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("answer")) { Yield(); } return gates.Open.Count * 10; } private int Scale(ArcTests.Gates gates, int factor) { scale: while (!gates.Open.Contains("scale")) { Yield(); } return factor * 100; } private int Count() { return ++Calls; } protected override void Run(ArcTests.Gates gates) { string mode = gates.Open.Contains("fast") ? "fast" : gates.Open.Contains("slow") ? "slow" : "other"; int value = mode switch { "fast" => Count(), "slow" => Ask(gates), string text when text.Length > 3 => Scale(gates, text.Length), _ => -1, }; Log.Add(mode + " " + value + " " + Calls); } } /// /// Adds to a field and a local, and fills an array slot, from passages that pause. /// public partial class AssignAskScript : Arc { [Scratch] public readonly List Log = new List(); public int Total = 5; public int[] Numbers = new int[4]; public int Calls; public string Name; private int Ask(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("answer")) { Yield(); } return gates.Open.Count * 10; } private string Describe(ArcTests.Gates gates) { describe: while (!gates.Open.Contains("describe")) { Yield(); } return "described"; } private int Next() { return ++Calls; } protected override void Run(ArcTests.Gates gates) { field: Total += Ask(gates); local: int sum = 1; sum += Ask(gates); slot: Numbers[Next()] = Ask(gates); name: Name ??= Describe(gates); Log.Add(Total + " " + sum + " " + Numbers[1] + " " + Calls + " " + Name); } } /// /// Takes a pair from passages, into new locals and into existing ones. /// public partial class PairAskScript : Arc { [Scratch] public readonly List Log = new List(); public int Total; private (int, int) Range(ArcTests.Gates gates) { range: while (!gates.Open.Contains("answer")) { Yield(); } return (gates.Open.Count, gates.Open.Count * 10); } protected override void Run(ArcTests.Gates gates) { fresh: var (low, high) = Range(gates); pause: Yield(); Log.Add(low + "-" + high); int other; existing: (Total, other) = Range(gates); Log.Add(Total + "-" + other); } } /// /// Calls passages that hand back values through out parameters, in each form a call can take. /// public partial class OutParameterScript : Arc { [Scratch] public readonly List Log = new List(); public int Stored; private bool TryPick(ArcTests.Gates gates, out int choice) { choice = 1; pick: while (!gates.Open.Contains("answer")) { Yield(); } choice = gates.Open.Count * 10; return gates.Open.Contains("accept"); } private void Fill(ArcTests.Gates gates, out int number, out string text) { number = 5; text = "early"; if (gates.Open.Contains("skip")) { return; } fill: while (!gates.Open.Contains("fill")) { Yield(); } number++; text = "filled"; } private void Relay(ArcTests.Gates gates, out int number) { relay: Fill(gates, out number, out string ignored); } protected override void Run(ArcTests.Gates gates) { header: if (TryPick(gates, out int choice)) { Log.Add("picked " + choice); } else { Log.Add("declined " + choice); } field: TryPick(gates, out Stored); Log.Add("stored " + Stored); declared: bool ok = TryPick(gates, out var again); Log.Add("again " + again + " " + ok); both: Fill(gates, out int number, out string text); Log.Add(number + " " + text); relayed: Relay(gates, out int relayedNumber); Log.Add("relayed " + relayedNumber); } } /// /// Passes a local and a field by reference to a passage that changes it before and after a pause. /// public partial class RefParameterScript : Arc { [Scratch] public readonly List Log = new List(); public int Total = 7; private void Bump(ArcTests.Gates gates, ref int counter) { counter += 1; bump: while (!gates.Open.Contains("go")) { Yield(); } counter *= 10; } private void Forward(ArcTests.Gates gates, ref int counter) { forward: Bump(gates, ref counter); counter += 5; } protected override void Run(ArcTests.Gates gates) { int local = 2; call: Bump(gates, ref local); Log.Add("local " + local); field: Bump(gates, ref Total); Log.Add("total " + Total); relayed: Forward(gates, ref local); Log.Add("relayed " + local); } } /// /// Passes arguments that have side effects to passages that pause, with and without using them afterward. /// public partial class ArgumentEffectScript : Arc { [Scratch] public readonly List Log = new List(); public int Calls; private int Next() { return ++Calls; } private void Hold(ArcTests.Gates gates, int value) { hold: while (!gates.Open.Contains("go")) { Yield(); } } private void Show(ArcTests.Gates gates, in int amount, string label = "amount") { show: while (!gates.Open.Contains("show")) { Yield(); } Log.Add(label + " " + amount); } private void Pass(ArcTests.Gates gates, in int amount) { pass: Show(gates, in amount); } protected override void Run(ArcTests.Gates gates) { unused: Hold(gates, Next()); shown: Show(gates, Next()); named: Show(gates, label: "named", amount: Next()); passed: int local = Next(); Pass(gates, in local); } } /// /// Reads a caller's field through a ref readonly parameter after a pause. /// public partial class ReadonlyRefScript : Arc { [Scratch] public readonly List Log = new List(); public int Level = 4; private void Watch(ArcTests.Gates gates, ref readonly int level) { watch: while (!gates.Open.Contains("go")) { Yield(); } Log.Add("level " + level); } protected override void Run(ArcTests.Gates gates) { watching: Watch(gates, in Level); } } /// /// Pauses at a bare Yield() step, and jumps back to that step until a gate opens. /// public partial class StepJumpScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { Log.Add("start"); retry: Yield(); Log.Add("woke"); if (!gates.Open.Contains("go")) { goto retry; } Log.Add("end"); } } /// /// Gives two sibling blocks a step with the same label, each jumping back to its own until a gate opens. /// public partial class SiblingLabelScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("left")) { again: Yield(); Log.Add("left"); if (!gates.Open.Contains("stop")) { goto again; } } else { again: Yield(); Log.Add("right"); if (!gates.Open.Contains("stop")) { goto again; } } Log.Add("end"); } } /// /// Gives a block that never pauses a label of the same name as a step in its sibling block. /// public partial class CopiedLabelScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { if (gates.Open.Contains("left")) { again: Log.Add("copied"); if (Log.Count < 3) { goto again; } } else { again: Yield(); Log.Add("step"); } Log.Add("end"); } } /// /// Jumps back to a label on an ordinary statement, ahead of the step that pauses. /// public partial class PlainLabelJumpScript : Arc { [Scratch] public readonly List Log = new List(); protected override void Run(ArcTests.Gates gates) { again: Log.Add("body"); hold: Yield(); if (Log.Count < 3) { goto again; } Log.Add("end"); } } /// /// Offers a passage for other arcs to run, which keeps a local across its pause and changes this arc's own state. /// public partial class WorkerScript : Arc { [Scratch] public readonly List Log = new List(); public int Total; public int Add(ArcTests.Gates gates, int amount) { int seen = 0; wait: while (!gates.Open.Contains("go")) { seen++; Yield(); } Total += amount; Log.Add("added " + amount + " after " + seen); return Total; } protected override void Run(ArcTests.Gates gates) { } } /// /// Runs the passage of two other arcs of one class in turn. /// public partial class ClientScript : Arc { [Scratch] public readonly List Log = new List(); public WorkerScript First = new WorkerScript(); public WorkerScript Second = new WorkerScript(); protected override void Run(ArcTests.Gates gates) { int before = 3; one: int a = First.Add(gates, 5); two: int b = Second.Add(gates, 7); Log.Add("a " + a + " b " + b + " before " + before); } } /// /// Runs the passage of one other arc twice from one call. /// public partial class RepeatingClientScript : Arc { public WorkerScript Worker = new WorkerScript(); protected override void Run(ArcTests.Gates gates) { for (int i = 0; i < 2; i++) { Worker.Add(gates, 10); } } } /// /// Calls a passage with params arguments, one of which has an effect. /// public partial class ParamsScript : Arc { [Scratch] public readonly List Log = new List(); public int Evaluations; private string Next() { Evaluations++; return "n" + Evaluations; } private void Say(ArcTests.Gates gates, params string[] lines) { wait: while (!gates.Open.Contains("go")) { Yield(); } Log.Add(string.Join(",", lines)); } protected override void Run(ArcTests.Gates gates) { say: Say(gates, "a", Next()); } } /// /// Holds a passage for the scripts that derive from it, which keeps a local across its pause. /// public abstract partial class StepsArc : Arc { [Scratch] public readonly List Log = new List(); protected void Hold(ArcTests.Gates gates, string name) { int seen = 0; wait: while (!gates.Open.Contains("go")) { seen++; Yield(); } Log.Add(name + " held " + seen); } } /// /// Runs the passage of the class it derives from, twice. /// public partial class HoldingScript : StepsArc { protected override void Run(ArcTests.Gates gates) { first: Hold(gates, "first"); second: Hold(gates, "second"); } } /// /// Runs the same inherited passage as HoldingScript. /// public partial class OtherHoldingScript : StepsArc { protected override void Run(ArcTests.Gates gates) { only: Hold(gates, "only"); } } /// /// Derives from a class that derives from the one with the passage. /// public abstract partial class MoreStepsArc : StepsArc { public int Extra; } public partial class DeepHoldingScript : MoreStepsArc { protected override void Run(ArcTests.Gates gates) { deep: Hold(gates, "deep"); } } /// /// Runs a passage of its own that runs the passage of another arc. /// public partial class RelayScript : Arc { public WorkerScript Worker = new WorkerScript(); private void Relay(ArcTests.Gates gates) { int through = Worker.Add(gates, 1); Worker.Log.Add("through " + through); } protected override void Run(ArcTests.Gates gates) { Relay(gates); } } /// /// Pauses until its gate opens, then throws. Counts how often its code before the throw ran. /// public partial class FaultingScript : Arc { public int Attempts; protected override void Run(ArcTests.Gates gates) { hold: while (!gates.Open.Contains("finish")) { if (gates.Open.Contains("explode")) { Attempts++; throw new InvalidOperationException("boom"); } Yield(); } } } /// /// Throws like FaultingScript, and says which area it was in. /// public partial class NotingScript : Arc { public string Area = "cellar"; protected override void AddFaultNotes(Dictionary notes) { notes["Area"] = Area; } protected override void Run(ArcTests.Gates gates) { hold: while (!gates.Open.Contains("finish")) { if (gates.Open.Contains("explode")) { throw new InvalidOperationException("boom"); } Yield(); } } } /// /// Throws like FaultingScript, and then throws again when asked to say what it was doing. /// public partial class BrokenNotingScript : Arc { protected override void AddFaultNotes(Dictionary notes) { notes["Before"] = "kept"; throw new InvalidOperationException("notes"); } protected override void Run(ArcTests.Gates gates) { hold: while (!gates.Open.Contains("finish")) { if (gates.Open.Contains("explode")) { throw new InvalidOperationException("boom"); } Yield(); } } } /// /// Records the times the latest tick gave it while it waits. /// public partial class ClockScript : Arc { public long SeenDeltaTimeTicks; public long SeenGameTicks; protected override void Run(ArcTests.Gates gates) { hold: while (!gates.Open.Contains("finish")) { SeenDeltaTimeTicks = DeltaTime.Ticks; SeenGameTicks = GameTime.Ticks; Yield(); } } } /// /// Waits a second of game time, keeping its deadline in a local across the pause. /// public partial class DeadlineScript : Arc { public bool Done; protected override void Run(ArcTests.Gates gates) { Duration deadline = GameTime + Duration.FromSeconds(1); wait: while (GameTime < deadline) { Yield(); } Done = true; } } /// /// Offers a passage for other arcs to run, which records the times it sees once its gate opens. /// public partial class TimeWorkerScript : Arc { public long SeenDeltaTimeTicks; public long SeenGameTicks; public void Note(ArcTests.Gates gates) { wait: while (!gates.Open.Contains("go")) { Yield(); } SeenDeltaTimeTicks = DeltaTime.Ticks; SeenGameTicks = GameTime.Ticks; } protected override void Run(ArcTests.Gates gates) { } } /// /// Runs the passage of another arc, which is never ticked itself. /// public partial class TimeClientScript : Arc { public TimeWorkerScript Worker = new TimeWorkerScript(); protected override void Run(ArcTests.Gates gates) { note: Worker.Note(gates); } } /// /// Ticks the arc it holds before or after its own script, which waits for that arc to finish. /// public partial class ParentScript : Arc { [Scratch] public readonly List Log = new List(); public FaultingScript Child = new FaultingScript(); public bool ChildFirst = true; protected override bool TickSelf(ArcTests.Gates gates, TickContext tick) { if (ChildFirst) { Child.Tick(gates, tick); Log.Add("child"); return base.TickSelf(gates, tick); } bool finished = base.TickSelf(gates, tick); Child.Tick(gates, tick); Log.Add("child"); return finished; } protected override void Run(ArcTests.Gates gates) { Log.Add("start"); wait: while (!Child.Finished) { Log.Add("self"); Yield(); } Log.Add("end"); } } /// /// Ticks the arc it holds instead of running its own script, and is done when that arc is. /// public partial class SkippingScript : Arc { public FaultingScript Child = new FaultingScript(); public int Turns; public bool Ran; protected override bool TickSelf(ArcTests.Gates gates, TickContext tick) { ++Turns; Child.Tick(gates, tick); return Child.Finished; } protected override void Run(ArcTests.Gates gates) { Ran = true; } } /// /// Ticks the arc it holds and then throws. /// public partial class ThrowingTurnScript : Arc { public FaultingScript Child = new FaultingScript(); public int Turns; protected override bool TickSelf(ArcTests.Gates gates, TickContext tick) { ++Turns; Child.Tick(gates, tick); throw new InvalidOperationException("turn"); } protected override void Run(ArcTests.Gates gates) { } } /// /// Ticks itself. /// public partial class SelfTickingScript : Arc { protected override bool TickSelf(ArcTests.Gates gates, TickContext tick) { Tick(gates, tick); return false; } protected override void Run(ArcTests.Gates gates) { } } /// /// Ticks another arc of its own class, which can tick it back. /// public partial class LoopingScript : Arc { public LoopingScript Other; public int Turns; protected override bool TickSelf(ArcTests.Gates gates, TickContext tick) { ++Turns; Other?.Tick(gates, tick); return false; } protected override void Run(ArcTests.Gates gates) { } } }