import grammar from "./grammar.ohm-bundle"; import { Affinity, ElementalType, MarkdownContext, MarkdownOutput, MeteredResource, NumberSign, Operands, ParseContext, Resource, UnmeteredResource, } from "../model/Messages"; import {IterationNode, Node, NonterminalNode, TerminalNode} from "ohm-js"; import {CharacterPrivacy, CharacterSide} from "../model/Character"; import {ClockMode} from "../model/GameState"; export const parser = grammar.createSemantics() export interface ParserNode extends Node { element: ElementalType|null affinity: Affinity sign: NumberSign numberValue: number identifier: string resource: Resource|null // TODO: Implement all of the things listed below. operands: Operands blockTargets: Operands blockSources: Operands currentValue: number|null maxValue: number|null textValue: string silenced: boolean failReason: unknown // TODO: create an enum for this // TODO: create rules for these to describe clocks/timers/items/statuses/characters and implement them nameText: string descriptionText: string side: CharacterSide privacy: CharacterPrivacy clockMode: ClockMode timerDirection: unknown // TODO: create an enum for this timerDurationMs: number // TODO: make sure that FP and UP spent gets saved in the appropriate counters evaluate(ctx: ParseContext): ParseContext renderMarkdown(ctx: MarkdownContext): MarkdownOutput } parser.addAttribute("element", { fireElement(): ElementalType.Fire { return ElementalType.Fire }, waterElement(): ElementalType.Water { return ElementalType.Water }, lightningElement(): ElementalType.Lightning { return ElementalType.Lightning }, iceElement(): ElementalType.Ice { return ElementalType.Ice }, earthElement(): ElementalType.Earth { return ElementalType.Earth }, windElement(): ElementalType.Wind { return ElementalType.Wind }, lightElement(): ElementalType.Light { return ElementalType.Light }, darkElement(): ElementalType.Dark { return ElementalType.Dark }, physicalElement(): ElementalType.Physical { return ElementalType.Physical }, nonElement(): ElementalType.Nonelemental { return ElementalType.Nonelemental }, healingElement(): ElementalType.Healing { return ElementalType.Healing }, elementalType(elementNode: NonterminalNode): ElementalType { const element = (elementNode as ParserNode).element if (element === null) { throw Error("Unexpectedly null element when an element was specified") } return element }, DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null { return (elementalType as ParserNode).element }, DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): ElementalType | null { return (elementalType as ParserNode).element }, DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null { return (elementalType as ParserNode).element }, _iter(...children: Node[]): ElementalType|null { if (this.isOptional() && children.length === 0) { return null } else if (this.isOptional() && children.length === 1) { return (children[0] as ParserNode).element } else { throw Error(`No idea what to say ${this.ctorName} iteration node's element is when there are multiple children`) } }, _nonterminal(): never { throw Error(`No idea what to say ${this.ctorName} nonterminal node's element is`) }, _terminal(): never { throw Error(`No idea what to say ${this.ctorName} terminal node's element is`) } }) parser.addAttribute("affinity", { absorbAffinity(): Affinity.Absorbs { return Affinity.Absorbs }, immuneAffinity(): Affinity.Immune { return Affinity.Immune }, resistAffinity(): Affinity.Resistant { return Affinity.Resistant }, vulnerableAffinity(): Affinity.Vulnerable { return Affinity.Vulnerable }, normalAffinity(): Affinity.Normal { return Affinity.Normal }, affinity(affinityNode: NonterminalNode): Affinity { return (affinityNode as ParserNode).affinity }, DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Affinity { return (affinity as ParserNode).affinity }, DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): Affinity { return (affinity as ParserNode).affinity }, DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Affinity { return (affinity as ParserNode).affinity }, _iter(...children: Node[]): Affinity { if (this.isOptional() && children.length === 0) { return Affinity.Normal } else if (this.isOptional() && children.length === 1) { return (children[0] as ParserNode).affinity } else { throw Error(`No idea what to say ${this.ctorName} iteration node's affinity is when there are multiple children`) } }, _nonterminal(): never { throw Error(`No idea what to say ${this.ctorName} nonterminal node's affinity is`) }, _terminal(): never { throw Error(`No idea what to say ${this.ctorName} terminal node's affinity is`) } }) parser.addAttribute("sign", { deltaOperator(oper: NonterminalNode): NumberSign { return (oper as ParserNode).sign }, plus(): NumberSign.Positive { return NumberSign.Positive }, minus(): NumberSign.Negative { return NumberSign.Negative }, StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): NumberSign { return (deltaOperator as ParserNode).sign }, StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): NumberSign { return (delta as ParserNode).sign }, _iter(): never { throw Error(`No idea what to say ${this.ctorName} iteration node's number sign is`) }, _nonterminal(): never { throw Error(`No idea what to say ${this.ctorName} nonterminal node's number sign is`) }, _terminal(): never { throw Error(`No idea what to say ${this.ctorName} terminal node's number sign is`) } }) parser.addAttribute("numberValue", { integer(digits: IterationNode): number { return parseInt(digits.sourceString) }, Delta(sign: NonterminalNode, integer: NonterminalNode): number { const number = (integer as ParserNode).numberValue switch ((sign as ParserNode).sign) { case NumberSign.Negative: return -number case NumberSign.Positive: return number } }, DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): number { return (delta as ParserNode).numberValue }, DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): number { return (delta as ParserNode).numberValue }, DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): number { return (delta as ParserNode).numberValue }, MaxValue(separator: NonterminalNode, integer: NonterminalNode): number { return (integer as ParserNode).numberValue; }, SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): number { return (integer as ParserNode).numberValue; }, SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, resource: NonterminalNode): number { return (integer as ParserNode).numberValue; }, SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): number { return (integer as ParserNode).numberValue; }, SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): number { return (maxvalue as ParserNode).numberValue; }, SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, resource: NonterminalNode): number { return (maxvalue as ParserNode).numberValue; }, SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): number { return (maxvalue as ParserNode).numberValue; }, StatusOrItemCounterUnwrapped(x: IterationNode, integer: NonterminalNode): number { return (integer as ParserNode).numberValue }, StatusOrItemCounterWrapped(lParen: TerminalNode, counter: NonterminalNode, rParen: TerminalNode): number { return (counter as ParserNode).numberValue }, StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number { return (counter as ParserNode).numberValue }, StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): number { return (delta as ParserNode).numberValue }, StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number { return (counter as ParserNode).numberValue }, _iter(): never { throw Error(`No idea what to say ${this.ctorName} iteration node's number value is`) }, _nonterminal(): never { throw Error(`No idea what to say ${this.ctorName} nonterminal node's number value is`) }, _terminal(): never { throw Error(`No idea what to say ${this.ctorName} terminal node's number value is`) } }) parser.addAttribute("identifier", { identifier(): string { return this.sourceString }, StatusOrItemDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): string { return (identifier as ParserNode).identifier }, StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): string { return (identifier as ParserNode).identifier }, StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): string { return (identifier as ParserNode).identifier }, _iter(): never { throw Error(`No idea what to say ${this.ctorName} iteration node's identifier value is`) }, _nonterminal(): never { throw Error(`No idea what to say ${this.ctorName} nonterminal node's identifier value is`) }, _terminal(): never { throw Error(`No idea what to say ${this.ctorName} terminal node's identifier value is`) } }) parser.addAttribute("resource", { BpResource(): MeteredResource.Blood { return MeteredResource.Blood }, FabulaResource(): UnmeteredResource.Fabula { return UnmeteredResource.Fabula; }, HpResource(): MeteredResource.Health { return MeteredResource.Health; }, IpResource(): MeteredResource.Items { return MeteredResource.Items; }, LevelResource(): UnmeteredResource.Level { return UnmeteredResource.Level; }, MaterialsResource(): UnmeteredResource.Materials { return UnmeteredResource.Materials; }, MeteredResource(res: NonterminalNode): MeteredResource { const r = (res as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, MoneyResource(): UnmeteredResource.Zenit { return UnmeteredResource.Zenit; }, MpResource(): MeteredResource.Magic { return MeteredResource.Magic; }, Resource(res: NonterminalNode): Resource { const r = (res as ParserNode).resource; if (r === null) { throw Error("unexpected null resource in resource node") } return r }, SpResource(type: NonterminalNode): UnmeteredResource.Special|UnmeteredResource.Fabula|UnmeteredResource.Ultima { const r = (type as ParserNode).resource; switch (r) { case UnmeteredResource.Special: case UnmeteredResource.Fabula: case UnmeteredResource.Ultima: return r default: throw Error("unexpected non-SP resources in an SP node") } }, SpecialResource(): UnmeteredResource.Special { return UnmeteredResource.Special; }, TpResource(): MeteredResource.Turns { return MeteredResource.Turns; }, UltimaResource(): Resource { return UnmeteredResource.Ultima; }, UnmeteredResource(res: NonterminalNode): UnmeteredResource { const r = (res as ParserNode).resource; switch (r) { case UnmeteredResource.Fabula: case UnmeteredResource.Ultima: case UnmeteredResource.Zenit: case UnmeteredResource.Materials: case UnmeteredResource.Special: case UnmeteredResource.Level: return r default: throw Error("unexpected metered resource in unmetered resource node") } }, XpResource(): MeteredResource.Experience { return MeteredResource.Experience; }, ZpResource(): MeteredResource.Zero { return MeteredResource.Zero; }, DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): Resource|null { return (resource as ParserNode).resource }, DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, resource: NonterminalNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): MeteredResource { const r = (resource as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, resource: NonterminalNode): MeteredResource { const r = (resource as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): MeteredResource { const r = (resource as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, _iter(...children: Node[]): Resource|null { if (this.isOptional() && children.length === 0) { return null } else if (this.isOptional() && children.length === 1) { return (children[0] as ParserNode).resource } else { throw Error(`No idea what to say ${this.ctorName} iteration node's resource is when there are multiple children`) } }, SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): MeteredResource { const r = (resource as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): MeteredResource { const r = (resource as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): MeteredResource { const r = (resource as ParserNode).resource; switch (r) { case MeteredResource.Health: case MeteredResource.Magic: case MeteredResource.Items: case MeteredResource.Experience: case MeteredResource.Zero: case MeteredResource.Turns: case MeteredResource.Segments: case MeteredResource.Blood: return r default: throw Error("unexpected unmetered resource in metered resource node") } }, ClearOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, nul: NonterminalNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, ClearOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, nul: NonterminalNode, resource: NonterminalNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, ClearOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, nul: NonterminalNode): Resource { const r = (resource as ParserNode).resource if (r === null) { throw Error("unexpected null resource in required resource node") } return r }, })