feat: yarn return
This commit is contained in:
parent
4d3faa3e4e
commit
a226a9516c
|
|
@ -1,4 +1,4 @@
|
|||
import type { YarnDocument, Statement, Line, Option } from "../model/ast";
|
||||
import type {YarnDocument, Statement, Line, Option, YarnNode} from "../model/ast";
|
||||
import type { IRProgram, IRNode, IRNodeGroup, IRInstruction } from "./ir";
|
||||
|
||||
export interface CompileOptions {
|
||||
|
|
@ -31,13 +31,8 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|||
nodesByTitle.get(node.title)!.push(node);
|
||||
}
|
||||
|
||||
for (const [title, nodesWithSameTitle] of nodesByTitle) {
|
||||
// If only one node with this title, treat as regular node
|
||||
if (nodesWithSameTitle.length === 1) {
|
||||
const node = nodesWithSameTitle[0];
|
||||
const instructions: IRInstruction[] = [];
|
||||
let onceCounter = 0;
|
||||
function emitBlock(stmts: Statement[]): IRInstruction[] {
|
||||
function emitBlock(stmts: Statement[], node: YarnNode): IRInstruction[] {
|
||||
const block: IRInstruction[] = [];
|
||||
for (const s of stmts) {
|
||||
switch (s.type) {
|
||||
|
|
@ -56,6 +51,9 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|||
case "Detour":
|
||||
block.push({ op: "detour", target: s.target });
|
||||
break;
|
||||
case "Return":
|
||||
block.push({ op: "return" });
|
||||
break;
|
||||
case "OptionGroup": {
|
||||
// Add #lastline tag to the most recent line, if present
|
||||
for (let i = block.length - 1; i >= 0; i--) {
|
||||
|
|
@ -72,18 +70,18 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|||
}
|
||||
block.push({
|
||||
op: "options",
|
||||
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body) })),
|
||||
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body, node) })),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "If":
|
||||
block.push({
|
||||
op: "if",
|
||||
branches: s.branches.map((b) => ({ condition: b.condition, block: emitBlock(b.body) })),
|
||||
branches: s.branches.map((b) => ({ condition: b.condition, block: emitBlock(b.body, node) })),
|
||||
});
|
||||
break;
|
||||
case "Once":
|
||||
block.push({ op: "once", id: genOnce({ node: node.title, index: onceCounter++ }), block: emitBlock(s.body) });
|
||||
block.push({ op: "once", id: genOnce({ node: node.title, index: onceCounter++ }), block: emitBlock(s.body, node) });
|
||||
break;
|
||||
case "Enum":
|
||||
// Enums are metadata, skip during compilation (already stored in program.enums)
|
||||
|
|
@ -92,7 +90,15 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|||
}
|
||||
return block;
|
||||
}
|
||||
instructions.push(...emitBlock(node.body));
|
||||
|
||||
for (const [title, nodesWithSameTitle] of nodesByTitle) {
|
||||
// If only one node with this title, treat as regular node
|
||||
if (nodesWithSameTitle.length === 1) {
|
||||
const node = nodesWithSameTitle[0];
|
||||
const instructions: IRInstruction[] = [];
|
||||
|
||||
onceCounter = 0;
|
||||
instructions.push(...emitBlock(node.body, node));
|
||||
const irNode: IRNode = {
|
||||
title: node.title,
|
||||
instructions,
|
||||
|
|
@ -106,61 +112,9 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
|||
const groupNodes: IRNode[] = [];
|
||||
for (const node of nodesWithSameTitle) {
|
||||
const instructions: IRInstruction[] = [];
|
||||
let onceCounter = 0;
|
||||
function emitBlock(stmts: Statement[]): IRInstruction[] {
|
||||
const block: IRInstruction[] = [];
|
||||
for (const s of stmts) {
|
||||
switch (s.type) {
|
||||
case "Line":
|
||||
{
|
||||
const line = s as Line;
|
||||
block.push({ op: "line", speaker: line.speaker, text: line.text, tags: ensureLineId(line.tags), markup: line.markup });
|
||||
}
|
||||
break;
|
||||
case "Command":
|
||||
block.push({ op: "command", content: s.content });
|
||||
break;
|
||||
case "Jump":
|
||||
block.push({ op: "jump", target: s.target });
|
||||
break;
|
||||
case "Detour":
|
||||
block.push({ op: "detour", target: s.target });
|
||||
break;
|
||||
case "OptionGroup": {
|
||||
for (let i = block.length - 1; i >= 0; i--) {
|
||||
const ins = block[i];
|
||||
if (ins.op === "line") {
|
||||
const tags = new Set(ins.tags ?? []);
|
||||
if (![...tags].some((x) => x === "lastline" || x === "#lastline")) {
|
||||
tags.add("lastline");
|
||||
}
|
||||
ins.tags = Array.from(tags);
|
||||
break;
|
||||
}
|
||||
if (ins.op !== "command") break;
|
||||
}
|
||||
block.push({
|
||||
op: "options",
|
||||
options: s.options.map((o: Option) => ({ text: o.text, tags: ensureLineId(o.tags), css: (o as any).css, markup: o.markup, condition: o.condition, block: emitBlock(o.body) })),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "If":
|
||||
block.push({
|
||||
op: "if",
|
||||
branches: s.branches.map((b) => ({ condition: b.condition, block: emitBlock(b.body) })),
|
||||
});
|
||||
break;
|
||||
case "Once":
|
||||
block.push({ op: "once", id: genOnce({ node: node.title, index: onceCounter++ }), block: emitBlock(s.body) });
|
||||
break;
|
||||
case "Enum":
|
||||
break;
|
||||
}
|
||||
}
|
||||
return block;
|
||||
}
|
||||
instructions.push(...emitBlock(node.body));
|
||||
|
||||
onceCounter = 0;
|
||||
instructions.push(...emitBlock(node.body, node));
|
||||
groupNodes.push({
|
||||
title: node.title,
|
||||
instructions,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export type IRInstruction =
|
|||
| { op: "command"; content: string }
|
||||
| { op: "jump"; target: string }
|
||||
| { op: "detour"; target: string }
|
||||
| { op: "return"; }
|
||||
| { op: "options"; options: Array<{ text: string; tags?: string[]; css?: string; markup?: MarkupParseResult; condition?: string; block: IRInstruction[] }> }
|
||||
| { op: "if"; branches: Array<{ condition: string | null; block: IRInstruction[] }> }
|
||||
| { op: "once"; id: string; block: IRInstruction[] };
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export type Statement =
|
|||
| OnceBlock
|
||||
| Jump
|
||||
| Detour
|
||||
| Return
|
||||
| EnumBlock;
|
||||
|
||||
import type { MarkupParseResult } from "../markup/types.js";
|
||||
|
|
@ -61,6 +62,11 @@ export interface Detour {
|
|||
target: string;
|
||||
}
|
||||
|
||||
export interface Return {
|
||||
type: "Return";
|
||||
target: string;
|
||||
}
|
||||
|
||||
export interface OptionGroup {
|
||||
type: "OptionGroup";
|
||||
options: Option[];
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type {
|
|||
OnceBlock,
|
||||
Jump,
|
||||
Detour,
|
||||
Return,
|
||||
EnumBlock,
|
||||
} from "../model/ast";
|
||||
|
||||
|
|
@ -174,6 +175,7 @@ class Parser {
|
|||
const cmd = this.take("COMMAND").text;
|
||||
if (cmd.startsWith("jump ")) return { type: "Jump", target: cmd.slice(5).trim() } as Jump;
|
||||
if (cmd.startsWith("detour ")) return { type: "Detour", target: cmd.slice(7).trim() } as Detour;
|
||||
if (cmd.startsWith("return")) return { type: "Return" } as Return;
|
||||
if (cmd.startsWith("if ")) return this.parseIfCommandBlock(cmd);
|
||||
if (cmd === "once") return this.parseOnceBlock();
|
||||
if (cmd.startsWith("enum ")) {
|
||||
|
|
|
|||
|
|
@ -122,13 +122,6 @@ export class YarnRunner {
|
|||
this.step();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current node title (may resolve to a node group).
|
||||
*/
|
||||
getCurrentNodeTitle(): string {
|
||||
return this.nodeTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a node title to an actual node (handling node groups).
|
||||
*/
|
||||
|
|
@ -498,6 +491,14 @@ export class YarnRunner {
|
|||
// resolveNode will handle node groups
|
||||
continue;
|
||||
}
|
||||
case "return": {
|
||||
const top = this.callStack.pop();
|
||||
if(!top) throw new Error("No call stack to return to");
|
||||
this.nodeTitle = top.title;
|
||||
this.ip = top.ip;
|
||||
this.currentNodeIndex = -1; // Reset node index for new resolution
|
||||
continue;
|
||||
}
|
||||
case "options": {
|
||||
const available = this.filterOptions(ins.options);
|
||||
if (available.length === 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue