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";
|
import type { IRProgram, IRNode, IRNodeGroup, IRInstruction } from "./ir";
|
||||||
|
|
||||||
export interface CompileOptions {
|
export interface CompileOptions {
|
||||||
|
|
@ -31,13 +31,8 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
||||||
nodesByTitle.get(node.title)!.push(node);
|
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;
|
let onceCounter = 0;
|
||||||
function emitBlock(stmts: Statement[]): IRInstruction[] {
|
function emitBlock(stmts: Statement[], node: YarnNode): IRInstruction[] {
|
||||||
const block: IRInstruction[] = [];
|
const block: IRInstruction[] = [];
|
||||||
for (const s of stmts) {
|
for (const s of stmts) {
|
||||||
switch (s.type) {
|
switch (s.type) {
|
||||||
|
|
@ -56,6 +51,9 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
||||||
case "Detour":
|
case "Detour":
|
||||||
block.push({ op: "detour", target: s.target });
|
block.push({ op: "detour", target: s.target });
|
||||||
break;
|
break;
|
||||||
|
case "Return":
|
||||||
|
block.push({ op: "return" });
|
||||||
|
break;
|
||||||
case "OptionGroup": {
|
case "OptionGroup": {
|
||||||
// Add #lastline tag to the most recent line, if present
|
// Add #lastline tag to the most recent line, if present
|
||||||
for (let i = block.length - 1; i >= 0; i--) {
|
for (let i = block.length - 1; i >= 0; i--) {
|
||||||
|
|
@ -72,18 +70,18 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
||||||
}
|
}
|
||||||
block.push({
|
block.push({
|
||||||
op: "options",
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case "If":
|
case "If":
|
||||||
block.push({
|
block.push({
|
||||||
op: "if",
|
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;
|
break;
|
||||||
case "Once":
|
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;
|
break;
|
||||||
case "Enum":
|
case "Enum":
|
||||||
// Enums are metadata, skip during compilation (already stored in program.enums)
|
// 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;
|
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 = {
|
const irNode: IRNode = {
|
||||||
title: node.title,
|
title: node.title,
|
||||||
instructions,
|
instructions,
|
||||||
|
|
@ -106,61 +112,9 @@ export function compile(doc: YarnDocument, opts: CompileOptions = {}): IRProgram
|
||||||
const groupNodes: IRNode[] = [];
|
const groupNodes: IRNode[] = [];
|
||||||
for (const node of nodesWithSameTitle) {
|
for (const node of nodesWithSameTitle) {
|
||||||
const instructions: IRInstruction[] = [];
|
const instructions: IRInstruction[] = [];
|
||||||
let onceCounter = 0;
|
|
||||||
function emitBlock(stmts: Statement[]): IRInstruction[] {
|
onceCounter = 0;
|
||||||
const block: IRInstruction[] = [];
|
instructions.push(...emitBlock(node.body, node));
|
||||||
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));
|
|
||||||
groupNodes.push({
|
groupNodes.push({
|
||||||
title: node.title,
|
title: node.title,
|
||||||
instructions,
|
instructions,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export type IRInstruction =
|
||||||
| { op: "command"; content: string }
|
| { op: "command"; content: string }
|
||||||
| { op: "jump"; target: string }
|
| { op: "jump"; target: string }
|
||||||
| { op: "detour"; 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: "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: "if"; branches: Array<{ condition: string | null; block: IRInstruction[] }> }
|
||||||
| { op: "once"; id: string; block: IRInstruction[] };
|
| { op: "once"; id: string; block: IRInstruction[] };
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export type Statement =
|
||||||
| OnceBlock
|
| OnceBlock
|
||||||
| Jump
|
| Jump
|
||||||
| Detour
|
| Detour
|
||||||
|
| Return
|
||||||
| EnumBlock;
|
| EnumBlock;
|
||||||
|
|
||||||
import type { MarkupParseResult } from "../markup/types.js";
|
import type { MarkupParseResult } from "../markup/types.js";
|
||||||
|
|
@ -61,6 +62,11 @@ export interface Detour {
|
||||||
target: string;
|
target: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Return {
|
||||||
|
type: "Return";
|
||||||
|
target: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface OptionGroup {
|
export interface OptionGroup {
|
||||||
type: "OptionGroup";
|
type: "OptionGroup";
|
||||||
options: Option[];
|
options: Option[];
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import type {
|
||||||
OnceBlock,
|
OnceBlock,
|
||||||
Jump,
|
Jump,
|
||||||
Detour,
|
Detour,
|
||||||
|
Return,
|
||||||
EnumBlock,
|
EnumBlock,
|
||||||
} from "../model/ast";
|
} from "../model/ast";
|
||||||
|
|
||||||
|
|
@ -174,6 +175,7 @@ class Parser {
|
||||||
const cmd = this.take("COMMAND").text;
|
const cmd = this.take("COMMAND").text;
|
||||||
if (cmd.startsWith("jump ")) return { type: "Jump", target: cmd.slice(5).trim() } as Jump;
|
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("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.startsWith("if ")) return this.parseIfCommandBlock(cmd);
|
||||||
if (cmd === "once") return this.parseOnceBlock();
|
if (cmd === "once") return this.parseOnceBlock();
|
||||||
if (cmd.startsWith("enum ")) {
|
if (cmd.startsWith("enum ")) {
|
||||||
|
|
|
||||||
|
|
@ -122,13 +122,6 @@ export class YarnRunner {
|
||||||
this.step();
|
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).
|
* Resolve a node title to an actual node (handling node groups).
|
||||||
*/
|
*/
|
||||||
|
|
@ -498,6 +491,14 @@ export class YarnRunner {
|
||||||
// resolveNode will handle node groups
|
// resolveNode will handle node groups
|
||||||
continue;
|
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": {
|
case "options": {
|
||||||
const available = this.filterOptions(ins.options);
|
const available = this.filterOptions(ins.options);
|
||||||
if (available.length === 0) {
|
if (available.length === 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue