UNPKG

yarn-spinner-runner-ts

Version:

TypeScript parser, compiler, and runtime for Yarn Spinner 3.x with React adapter [NPM package](https://www.npmjs.com/package/yarn-spinner-runner-ts)

410 lines (318 loc) 12.7 kB
# yarn-spinner-runner-ts TypeScript parser, compiler, and runtime for Yarn Spinner 3.x with React adapter. * [Github repository](https://github.com/oleksii-chekhovskyi/yarn-spinner-runner-ts) for more information. * [NPM package](https://www.npmjs.com/package/yarn-spinner-runner-ts) ## References * Old JS parser: `bondage.js` (Yarn 2.x) — [GitHub](https://github.com/mnbroatch/bondage.js/tree/master/src) * Official compiler (C#): YarnSpinner.Compiler — [GitHub](https://github.com/YarnSpinnerTool/YarnSpinner/tree/main/YarnSpinner.Compiler) * Existing dialogue runner API: YarnBound — [GitHub](https://github.com/mnbroatch/yarn-bound?tab=readme-ov-file) ## Features * ✅ Full Yarn Spinner 3.x syntax support * ✅ Parser for `.yarn` files → AST * ✅ Compiler: AST → Intermediate Representation (IR) * ✅ Runtime with `YarnRunner` class * ✅ React hook: `useYarnRunner()` * ✅ React components: `<DialogueView />`, `<DialogueScene />`, `<DialogueExample />` * ✅ Typing animation with configurable speeds, cursor styles, and auto-advance controls * ✅ Markup parsing with HTML formatting tags and CSS-ready spans * ✅ Expression evaluator for conditions * ✅ Command system with built-in handlers (`<<set>>`, `<<declare>>`, etc.) * ✅ Scene system with backgrounds and actor images (with configurable portrait cross-fades) * ✅ Custom CSS styling via `&css{}` attributes * ✅ Built-in functions (`visited`, `random`, `min`, `max`, etc.) * ✅ Support for: * Lines with speakers * Options with indented bodies * Inline option conditions via `[if expression]` * `<<if>>/<<elseif>>/<<else>>/<<endif>>` blocks * `<<once>>...<<endonce>>` blocks * `<<jump NodeName>>` commands * `<<detour NodeName>>` commands * Variables and expressions * Enums (`<<enum>>` blocks) * Smart variables (`<<declare $var = expr>>`) * Node groups with `when:` conditions * Tags and metadata on nodes, lines, and options * Custom commands ## Installation ```bash npm install npm run build ``` ## Quick Start ### Basic Usage ```typescript import { parseYarn, compile, YarnRunner } from "yarn-spinner-runner-ts"; const yarnText = ` title: Start --- Narrator: Hello! -> Option 1 Narrator: You chose option 1. -> Option 2 Narrator: You chose option 2. === `; const ast = parseYarn(yarnText); const program = compile(ast); const runner = new YarnRunner(program, { startAt: "Start", variables: { score: 10 }, functions: { add: (a: number, b: number) => a + b, }, handleCommand: (cmd, parsed) => { console.log("Command:", cmd); }, onStoryEnd: ({ variables, storyEnd }) => { console.log("Story ended!", storyEnd); console.log("Final variables:", variables); }, }); // Get current result console.log(runner.currentResult); // TextResult, OptionsResult, or CommandResult // Advance dialogue runner.advance(); // Continue text runner.advance(0); // Choose option 0 ``` > Variables passed from your host app can be provided as `score` or `$score`; the runner normalizes keys so either style works. ### Inline conditional options You can add a per-option condition by appending `[if expression]` to the option text. The expression is evaluated when the option list is emitted; options whose expression evaluates to `false` are dropped before the runner shows them. ```yarn title: Hub --- <<declare $hasBadge = false>> -> Ask about the badge [if $hasBadge] Narrator: You flash the badge. -> Offer a bribe Narrator: You slide some eddies across the table. === ``` Once some branch executes `<<set $hasBadge = true>>`, the badge option automatically appears alongside the other entries, without extra `<<if>>` blocks. ### Arithmetic assignments `<<set>>` accepts both `to` and `=` aliases and evaluates the expression on the right-hand side, so you can modify variables inline—operator precedence and parentheses all work the same way they do in Yarn Spinner: ```yarn <<set $reputation = $reputation - 25 >> <<set $score = ($score + 10) / 2>> Narrator: Current street cred: {$reputation}, score: {$score} ``` ### React Usage ```tsx import { parseYarn, compile, useYarnRunner, DialogueView } from "yarn-spinner-runner-ts"; import { parseScenes } from "yarn-spinner-runner-ts"; import type { SceneCollection } from "yarn-spinner-runner-ts"; function MyDialogue() { const [program] = useState(() => { const ast = parseYarn(yarnText); return compile(ast); }); const [scenes] = useState<SceneCollection>(() => { return parseScenes(sceneYamlText); }); const { result, advance } = useYarnRunner(program, { startAt: "Start", variables: { score: 10 }, }); return ( <DialogueView result={result} onAdvance={advance} scenes={scenes} /> ); } ``` ### Full Example Component ```tsx import { DialogueExample } from "yarn-spinner-runner-ts"; function App() { return <DialogueExample />; } ``` ### Typing Animation Set `enableTypingAnimation` on `DialogueView` to enable the `TypingText` component for typewriter-style delivery. Tweak props like `typingSpeed`, `showTypingCursor`, `cursorCharacter`, `autoAdvanceAfterTyping`, `autoAdvanceDelay`, and `pauseBeforeAdvance` to fine-tune behaviour, and see [Typing Animation (React)](./docs/typing-animation.md) for details. ### Browser Demo Run the interactive browser demo: ```bash npm run demo ``` This starts a Vite dev server with a live Yarn script editor and dialogue system. ## API Reference ### Parser * `parseYarn(text: string): YarnDocument` — Parse Yarn script text into AST ### Compiler * `compile(doc: YarnDocument, opts?: CompileOptions): IRProgram` — Compile AST to IR ### Runtime * `YarnRunner(program: IRProgram, options: RunnerOptions)` — Dialogue runner class * `currentResult: RuntimeResult | null` — Current dialogue state * `advance(optionIndex?: number): void` — Advance dialogue * `getVariable(name: string): unknown` — Get variable value * `setVariable(name: string, value: unknown): void` — Set variable value * `getVariables(): Readonly<Record<string, unknown>>` — Get all variables * `onStoryEnd?: (payload: { variables: Readonly<Record<string, unknown>>; storyEnd: true }) => void` — Handler called when story reaches its end, providing final variable state ### React Components * `useYarnRunner(program: IRProgram, options: RunnerOptions)` — React hook * Returns: `{ result: RuntimeResult | null, advance: (optionIndex?: number) => void, runner: YarnRunner }` * `<DialogueView result={...} onAdvance={...} scenes={...} />` — Ready-to-use dialogue component * `<DialogueScene sceneName={...} speaker={...} scenes={...} actorTransitionDuration={...} /> — Scene background, actor display, and portrait transitions` — Scene background and actor display * `<DialogueExample />` — Full example with editor ### Scene System * `parseScenes(input: string | Record<string, unknown>): SceneCollection` — Parse YAML scene configuration * `SceneCollection` — Type for scene configuration * `SceneConfig` — Type for individual scene config * `ActorConfig` — Type for actor configuration See [Scene and Actor Setup Guide](./docs/scenes-actors-setup.md) for detailed documentation. ### Expression Evaluator * `ExpressionEvaluator(variables, functions, enums?)` — Safe expression evaluator * Supports: `===`, `!==`, `<`, `>`, `<=`, `>=`, `&&`, `||`, `!` * Operator aliases: `eq/is`, `neq`, `gt`, `lt`, `lte`, `gte`, `and`, `or`, `not`, `xor` * Function calls: `functionName(arg1, arg2)` * Variables, numbers, strings, booleans * Enum support with shorthand (`MyEnum.Case`) ### Commands * `CommandHandler` — Command handler registry * Built-in: `<<set variable = value>>`, `<<declare $var = expr>>` * Register custom handlers: `handler.register("mycommand", (args) => { ... })` * `parseCommand(content: string): ParsedCommand` — Parse command string ### Built-in Functions The runtime includes these built-in functions: * `visited(nodeName)` — Check if a node was visited * `visited_count(nodeName)` — Get visit count for a node * `random()` — Random float 0-1 * `random_range(min, max)` — Random integer in range * `dice(sides)` — Roll a die * `min(a, b)`, `max(a, b)` — Min/max values * `round(n)`, `round_places(n, places)` — Rounding * `floor(n)`, `ceil(n)` — Floor/ceiling * `inc(n)`, `dec(n)` — Increment/decrement * `decimal(n)` — Convert to decimal * `int(n)` — Convert to integer * `string(n)`, `number(n)`, `bool(n)` — Type conversions ## Example Yarn Script ```yarn title: Start tags: #introduction #tutorial --- Narrator: Welcome! <<set score = 10>> <<if score >= 10>> Narrator: High score! <<else>> Narrator: Low score. <<endif>> <<declare $randomName = random_range(1, 3) == 1 ? "Alice" : "Bob">> Narrator: Your name is {$randomName}. -> Ask about features Player: What can this do? Narrator: Lots of things! -> Ask about commands Player: Tell me about commands. Narrator: Commands modify state. <<once>> Narrator: This only shows once! <<endonce>> <<jump NextNode>> === title: NextNode scene: scene1 --- Narrator: You've arrived at the next scene! === ``` ## CSS Styling You can apply custom CSS styles to nodes and options using the `&css{}` attribute: ```yarn title: StyledNode &css{background-color: #ff0000; color: white;} --- Narrator: This node has a red background. -> Option 1 &css{background-color: blue;} Narrator: You chose the blue option. === ``` Styles are merged with default styles, with custom styles taking precedence. See [CSS Attribute Documentation](./docs/css-attribute.md) for details. ## Scene Configuration Configure scenes and actors using YAML: ```yaml scenes: scene1: background: https://example.com/background1.jpg actors: special_npc: image: https://example.com/special-npc.png actors: Narrator: https://example.com/narrator.png Player: https://example.com/player.png ``` Use scenes in Yarn nodes: ```yarn title: MyNode scene: scene1 --- Narrator: This scene uses scene1's background and actors. === ``` See [Scene and Actor Setup Guide](./docs/scenes-actors-setup.md) for complete documentation. ## Project Structure ``` yarn-spinner/ ├── src/ │ ├── model/ # AST types │ ├── parse/ # Lexer and parser │ ├── compile/ # Compiler (AST → IR) │ ├── runtime/ # Runtime execution │ ├── scene/ # Scene system │ ├── react/ # React components │ └── tests/ # Test files ├── examples/ │ ├── yarn/ # Example Yarn scripts │ ├── browser/ # Browser demo (Vite) │ └── scenes/ # Scene configuration examples ├── docs/ # Documentation └── dist/ # Compiled output ``` ## Development ```bash npm run build # Build TypeScript npm run dev # Watch mode npm run lint # Run ESLint npm test # Run tests npm run demo # Start browser demo npm run demo:build # Build browser demo ``` ## Testing Tests are located in `src/tests/` and cover: * Basic dialogue flow * Options and branching * Variables and flow control * Commands (`<<set>>`, `<<declare>>`, etc.) * `<<once>>` blocks * `<<jump>>` and `<<detour>>` * Full featured Yarn scripts Run tests: ```bash npm test ``` ## Documentation Additional documentation is available in the `docs/` folder: * [Lines, Nodes, and Options](./docs/lines-nodes-and-options.md) * [Options](./docs/options.md) * [Jumps](./docs/jumps.md) * [Detour](./docs/detour.md) * [Logic and Variables](./docs/logic-and-variables.md) * [Flow Control](./docs/flow-control.md) * [Once Blocks](./docs/once.md) * [Smart Variables](./docs/smart-variables.md) * [Enums](./docs/enums.md) * [Commands](./docs/commands.md) * [Functions](./docs/functions.md) * [Node Groups](./docs/node-groups.md) * [Tags and Metadata](./docs/tags-metadata.md) * [CSS Attribute](./docs/css-attribute.md) * [Typing Animation (React)](./docs/typing-animation.md) * [Markup (Yarn Spinner)](./docs/markup.md) * [Actor Image Transitions](./docs/actor-transition.md) * [Scene and Actor Setup](./docs/scenes-actors-setup.md) ## License MIT