Tracker made in React for keeping track of HP and MP and so on.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
fabula-ultima-react/src/grammar/interpreter.ts

1538 lines
70 KiB

import grammar from "./grammar.ohm-bundle";
import {
Affinity,
ElementalType,
evaluateOperands,
EvaluationContext,
FailReason,
lookupOperands,
MarkdownContext,
MarkdownOutput,
NumberSign,
OperandItems,
Operands,
OperandSets,
Source,
Target,
} from "../model/Messages";
import {IterationNode, Node, NonterminalNode, TerminalNode} from "ohm-js";
import {
applyCharacterPrivacy,
CharacterPrivacy,
CharacterResources,
CharacterSide,
CharacterStatuses,
isZpReady
} from "../model/Character";
import {GameState, TimerDirection} from "../model/GameState";
import {
formatMeteredResource,
formatResourceDelta, formatResourceMax, formatResourceValue,
isMeteredResource,
isUnmeteredResource,
MeteredResource,
Resource,
UnmeteredResource
} from "../model/Resources";
import {ClockMode, ClockResources} from "../model/Clock";
import {isDefined} from "../types/type_check";
export const interpreter = grammar.createSemantics()
export interface InterpreterNode extends Node {
readonly element: ElementalType|null
readonly affinity: Affinity
readonly sign: NumberSign
readonly numberValue: number
readonly textValue: string
readonly identifier: string
readonly resource: Resource|null
readonly operands: Operands
readonly targets: Operands
readonly sources: Operands
readonly silenced: boolean
readonly currentValue: number
readonly maxValue: number
readonly failReason: FailReason
readonly side: CharacterSide
readonly privacy: CharacterPrivacy
// TODO: Implement all of the things listed below.
// TODO: create rules for these to describe clocks/timers/items/statuses/characters and implement them
readonly nameText: string
readonly descriptionText: string
// TODO: use for portraits, status icons, backdrops, BGM, and SFX
readonly url: string
readonly clockMode: ClockMode
readonly timerDirection: TimerDirection
readonly timerDurationMs: number
// TODO: add backdrop and music change and sfx commands
// TODO: make sure that FP and UP spent gets saved in the appropriate counters
evaluate(ctx: EvaluationContext): EvaluationContext
renderMarkdown(ctx: MarkdownContext): MarkdownOutput
}
export interface EvaluationNode extends InterpreterNode {
readonly args: {readonly ctx: EvaluationContext}
}
export interface MarkdownNode extends InterpreterNode {
readonly args: {readonly ctx: MarkdownContext}
}
interpreter.addAttribute<ElementalType|null>("element", {
fireElement(_: TerminalNode): ElementalType.Fire {
return ElementalType.Fire
},
waterElement(_: TerminalNode): ElementalType.Water {
return ElementalType.Water
},
lightningElement(_: TerminalNode): ElementalType.Lightning {
return ElementalType.Lightning
},
iceElement(_: TerminalNode): ElementalType.Ice {
return ElementalType.Ice
},
earthElement(_: TerminalNode): ElementalType.Earth {
return ElementalType.Earth
},
windElement(_: TerminalNode): ElementalType.Wind {
return ElementalType.Wind
},
lightElement(_: TerminalNode): ElementalType.Light {
return ElementalType.Light
},
darkElement(_: TerminalNode): ElementalType.Dark {
return ElementalType.Dark
},
physicalElement(_: TerminalNode): ElementalType.Physical {
return ElementalType.Physical
},
nonElement(_: TerminalNode): ElementalType.Nonelemental {
return ElementalType.Nonelemental
},
healingElement(_: TerminalNode): ElementalType.Healing {
return ElementalType.Healing
},
elementalType(elementNode: NonterminalNode): ElementalType {
const element = (elementNode as InterpreterNode).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 InterpreterNode).element
},
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, resource: IterationNode, elementalType: IterationNode): ElementalType | null {
return (elementalType as InterpreterNode).element
},
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, elementalType: IterationNode): ElementalType | null {
return (elementalType as InterpreterNode).element
},
_iter(...children: readonly Node[]): ElementalType|null {
if (this.isOptional() && children.length === 0) {
return null
} else if (this.isOptional() && children.length === 1) {
return (children[0] as InterpreterNode).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`)
}
})
interpreter.addAttribute<Affinity>("affinity", {
absorbAffinity(_: TerminalNode): Affinity.Absorbs {
return Affinity.Absorbs
},
immuneAffinity(_: TerminalNode): Affinity.Immune {
return Affinity.Immune
},
resistAffinity(_: TerminalNode): Affinity.Resistant {
return Affinity.Resistant
},
vulnerableAffinity(_: TerminalNode): Affinity.Vulnerable {
return Affinity.Vulnerable
},
normalAffinity(_: TerminalNode): Affinity.Normal {
return Affinity.Normal
},
affinity(affinityNode: NonterminalNode): Affinity {
return (affinityNode as InterpreterNode).affinity
},
DeltaOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, _elementalType: IterationNode): Affinity {
return (affinity as InterpreterNode).affinity
},
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, _resource: IterationNode, _elementalType: IterationNode): Affinity {
return (affinity as InterpreterNode).affinity
},
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, affinity: IterationNode, _elementalType: IterationNode): Affinity {
return (affinity as InterpreterNode).affinity
},
_iter(...children: readonly Node[]): Affinity {
if (this.isOptional() && children.length === 0) {
return Affinity.Normal
} else if (this.isOptional() && children.length === 1) {
return (children[0] as InterpreterNode).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`)
}
})
interpreter.addAttribute<NumberSign>("sign", {
deltaOperator(operator: NonterminalNode): NumberSign {
return (operator as InterpreterNode).sign
},
plus(_: TerminalNode): NumberSign.Positive {
return NumberSign.Positive
},
minus(_: TerminalNode): NumberSign.Negative {
return NumberSign.Negative
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): NumberSign {
return (delta as InterpreterNode).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`)
}
})
interpreter.addAttribute<number>("numberValue", {
integer(digits: IterationNode): number {
return parseInt(digits.sourceString)
},
Delta(sign: NonterminalNode, integer: NonterminalNode): number {
const number = (integer as InterpreterNode).numberValue
switch ((sign as InterpreterNode).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 InterpreterNode).numberValue
},
DeltaOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, delta: NonterminalNode, _affinity: IterationNode, _resource: IterationNode, _elementalType: IterationNode): number {
return (delta as InterpreterNode).numberValue
},
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, delta: NonterminalNode, _affinity: IterationNode, _elementalType: IterationNode): number {
return (delta as InterpreterNode).numberValue
},
MaxValue(separator: NonterminalNode, integer: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, _resource: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): number {
return (maxvalue as InterpreterNode).numberValue;
},
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, _resource: NonterminalNode): number {
return (maxvalue as InterpreterNode).numberValue;
},
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): number {
return (maxvalue as InterpreterNode).numberValue;
},
StatusOrItemCounterUnwrapped(x: IterationNode, integer: NonterminalNode): number {
return (integer as InterpreterNode).numberValue
},
StatusOrItemCounterWrapped(lParen: TerminalNode, counter: NonterminalNode, _rParen: TerminalNode): number {
return (counter as InterpreterNode).numberValue
},
StatusOrItemAddOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: IterationNode): number {
if (counter.numChildren === 0) {
return 0
}
return (counter.child(0)).numberValue
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, delta: NonterminalNode): number {
return (delta as InterpreterNode).numberValue
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
return (counter as InterpreterNode).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`)
}
})
interpreter.addAttribute<string>("textValue", {
identifier(_: TerminalNode, __: NonterminalNode): string {
return this.sourceString
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's text value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's text value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's text value is`)
}
})
interpreter.addAttribute<string>("identifier", {
identifier(_: TerminalNode, __: IterationNode): string {
return this.sourceString
},
StatusOrItemAddOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, _counter: NonterminalNode): string {
return (identifier as InterpreterNode).identifier
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, _delta: NonterminalNode): string {
return (identifier as InterpreterNode).identifier
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, _counter: NonterminalNode): string {
return (identifier as InterpreterNode).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`)
}
})
interpreter.addAttribute<Resource|null>("resource", {
BpResource(_: NonterminalNode): MeteredResource.Blood {
return MeteredResource.Blood
},
FabulaResource(_: NonterminalNode): UnmeteredResource.Fabula {
return UnmeteredResource.Fabula;
},
HpResource(_: NonterminalNode): MeteredResource.Health {
return MeteredResource.Health;
},
IpResource(_: NonterminalNode): MeteredResource.Items {
return MeteredResource.Items;
},
LevelResource(_: NonterminalNode): UnmeteredResource.Level {
return UnmeteredResource.Level;
},
MaterialsResource(_: NonterminalNode): UnmeteredResource.Materials {
return UnmeteredResource.Materials;
},
MeteredResource(res: NonterminalNode): MeteredResource {
const r = (res as InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in metered resource`)
}
return r
},
MoneyResource(_: NonterminalNode): UnmeteredResource.Zenit {
return UnmeteredResource.Zenit;
},
MpResource(_: NonterminalNode): MeteredResource.Magic {
return MeteredResource.Magic;
},
Resource(res: NonterminalNode): Resource {
const r = (res as InterpreterNode).resource;
if (r === null) {
throw Error("unexpected null resource in resource node")
}
return r
},
SegmentResource(_: NonterminalNode): MeteredResource.Segments {
return MeteredResource.Segments
},
SpResource(type: NonterminalNode): UnmeteredResource.Special|UnmeteredResource.Fabula|UnmeteredResource.Ultima {
const r = (type as InterpreterNode).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(_: TerminalNode): UnmeteredResource.Special {
return UnmeteredResource.Special;
},
TpResource(_: TerminalNode): MeteredResource.Turns {
return MeteredResource.Turns;
},
UltimaResource(_: TerminalNode): Resource {
return UnmeteredResource.Ultima;
},
OrderResource(_: TerminalNode): UnmeteredResource.Order {
return UnmeteredResource.Order
},
UnmeteredResource(res: NonterminalNode): UnmeteredResource {
const r = (res as InterpreterNode).resource;
if (!r || !isUnmeteredResource(r)) {
throw Error(`unexpected null or metered resource ${r} in max operation`)
}
return r
},
XpResource(_: TerminalNode): MeteredResource.Experience {
return MeteredResource.Experience;
},
ZpResource(_: TerminalNode): 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 InterpreterNode).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 InterpreterNode).resource
},
DeltaOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _elementalType: IterationNode): Resource {
const r = (resource as InterpreterNode).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 InterpreterNode).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 InterpreterNode).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 InterpreterNode).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 InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in max operation`)
}
return r
},
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, resource: NonterminalNode): MeteredResource {
const r = (resource as InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in max operation`)
}
return r
},
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, _maxvalue: NonterminalNode): MeteredResource {
const r = (resource as InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in max operation`)
}
return r
},
_iter(...children: readonly Node[]): Resource|null {
if (this.isOptional() && children.length === 0) {
return null
} else if (this.isOptional() && children.length === 1) {
return (children[0] as InterpreterNode).resource
} else {
throw Error(`No idea what to say ${this.ctorName} iteration node's resource is when there are multiple children`)
}
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's resource value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's resource value is`)
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, _colon: NonterminalNode, _value: NonterminalNode): MeteredResource {
const r = (resource as InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in metered set`)
}
return r
},
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, resource: NonterminalNode): MeteredResource {
const r = (resource as InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in metered set`)
}
return r
},
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, _value: NonterminalNode): MeteredResource {
const r = (resource as InterpreterNode).resource;
if (!r || !isMeteredResource(r)) {
throw Error(`unexpected null or unmetered resource ${r} in metered set`)
}
return r
},
ClearOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, _colon: NonterminalNode, _nul: NonterminalNode): Resource {
const r = (resource as InterpreterNode).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 InterpreterNode).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 InterpreterNode).resource
if (r === null) {
throw Error("unexpected null resource in required resource node")
}
return r
},
})
interpreter.addAttribute<Operands>("operands", {
CompleteOperation(silence: IterationNode, operation: NonterminalNode, _terminator: NonterminalNode): Operands {
return (operation as InterpreterNode).operands
},
Operation(operation: NonterminalNode): Operands {
return (operation as InterpreterNode).operands
},
ClearOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _nul: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
ClearOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _nul: NonterminalNode, _resource: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
ClearOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _nul: NonterminalNode): Operands {
return (operands as InterpreterNode).operands;
},
DeltaOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _element: IterationNode): Operands {
return (operands as InterpreterNode).operands;
},
DeltaOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _resource: IterationNode, _element: IterationNode): Operands {
return (operands as InterpreterNode).operands;
},
DeltaOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _element: IterationNode): Operands {
return (operands as InterpreterNode).operands;
},
FailOperation(operands: NonterminalNode, _colon: NonterminalNode, _fail: NonterminalNode): Operands {
return (operands as InterpreterNode).operands;
},
PrintOperation(_: NonterminalNode, __: NonterminalNode): Set<never> {
return OperandItems<never>()
},
SetMaxOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _max: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetMaxOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _max: NonterminalNode, _resource: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetMaxOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _max: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetMeteredOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _meter: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetMeteredOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _meter: NonterminalNode, _resource: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetMeteredOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _meter: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetSourceOperation(source: NonterminalNode, operands: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetTargetOperation(target: NonterminalNode, operands: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetValueOperation(operands: NonterminalNode, _space: NonterminalNode, _resource: NonterminalNode, _colon: NonterminalNode, _value: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetValueOperationAlternate(operands: NonterminalNode, _colon: NonterminalNode, _value: NonterminalNode, _resource: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
SetValueOperationAlternate2(operands: NonterminalNode, _colon: NonterminalNode, _resource: NonterminalNode, _value: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemCounterDeltaOperation(operands: NonterminalNode, _colon: NonterminalNode, _statusOrItem: NonterminalNode, _delta: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, _colon: NonterminalNode, _statusOrItem: NonterminalNode, _value: NonterminalNode): Operands {
return (operands as InterpreterNode).operands
},
StatusOrItemAddOperation(operands: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _statusOrItem: NonterminalNode, _counter: IterationNode): Operands {
return (operands as InterpreterNode).operands
},
Operands(arg0: NonterminalNode): Operands {
return (arg0.asIteration() as InterpreterNode).operands
},
_iter(...children: readonly Node[]): Operands {
return OperandSets(children.map((child) => (child as InterpreterNode).operands))
},
operand(operand: TerminalNode): Operands {
return (operand as InterpreterNode).operands
},
identifier(_: TerminalNode, __: NonterminalNode): Set<string> {
return OperandItems((this as InterpreterNode).identifier)
},
target(_: TerminalNode): Set<typeof Target> {
return OperandItems(Target)
},
source(_: TerminalNode): Set<typeof Source> {
return OperandItems(Source);
},
null(_: TerminalNode): Set<never> {
return OperandItems()
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's operands value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's operands value is`)
},
})
interpreter.addAttribute<Operands>("targets", {
Block(start: NonterminalNode, _terminator: NonterminalNode, _code: NonterminalNode, _end: NonterminalNode, _terminator2: NonterminalNode): Operands {
return (start as InterpreterNode).targets
},
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, _title: NonterminalNode): Operands {
return (target as InterpreterNode).operands
},
SetTargetOperation(_arg0: NonterminalNode, _arg1: NonterminalNode): Operands {
return (this as InterpreterNode).operands
},
_iter(): Operands {
throw Error(`No idea what to say ${this.ctorName} iteration node's block targets value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's block targets value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's block targets value is`)
},
})
interpreter.addAttribute<Operands>("sources", {
Block(start: NonterminalNode, _terminator: NonterminalNode, _code: NonterminalNode, _end: NonterminalNode, _terminator2: NonterminalNode): Operands {
return (start as InterpreterNode).sources
},
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, _target: IterationNode, _title: NonterminalNode): Operands {
return (source as InterpreterNode).operands
},
SetSourceOperation(_arg0: NonterminalNode, _arg1: NonterminalNode): Operands {
return (this as InterpreterNode).operands
},
_iter(): Operands {
throw Error(`No idea what to say ${this.ctorName} iteration node's block sources value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's block sources value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's block sources value is`)
}
})
interpreter.addAttribute<boolean>("silenced", {
Block(start: NonterminalNode, _terminator: NonterminalNode, _code: NonterminalNode, _end: NonterminalNode, _terminator2: NonterminalNode): boolean {
return (start as InterpreterNode).silenced
},
BlockStart(silenced: IterationNode, begin: NonterminalNode, source: IterationNode, target: IterationNode, _title: NonterminalNode): boolean {
return (target as InterpreterNode).silenced
},
CompleteOperation(silenced: IterationNode, _operation: NonterminalNode, _terminator: NonterminalNode): boolean {
return (silenced as InterpreterNode).silenced
},
silentOperator(_: TerminalNode): boolean {
return true
},
_iter(): boolean {
if (this.isOptional() && this.children.length === 0) {
return false
} else if (this.isOptional() && this.children.length === 1) {
return (this.children[0] as InterpreterNode).silenced
} 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 silenced status is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's silenced status is`)
}
})
interpreter.addAttribute<number>("currentValue", {
SetValueOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
SetValueOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, integer: NonterminalNode, _resource: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
SetValueOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, integer: NonterminalNode): number {
return (integer as InterpreterNode).numberValue;
},
MeteredValue(current: NonterminalNode, _max: NonterminalNode): number {
return (current as InterpreterNode).numberValue
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): number {
return (value as InterpreterNode).currentValue;
},
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, _resource: NonterminalNode): number {
return (value as InterpreterNode).currentValue;
},
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
return (value as InterpreterNode).currentValue;
},
StatusOrItemAddOperation(operands: NonterminalNode, colon: NonterminalNode, deltaOperator: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
return (counter as InterpreterNode).numberValue
},
StatusOrItemCounterSetOperation(operands: NonterminalNode, colon: NonterminalNode, identifier: NonterminalNode, counter: NonterminalNode): number {
return (counter as InterpreterNode).numberValue
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's current value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's current value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's current value is`)
}
})
interpreter.addAttribute<number>("maxValue", {
MaxValue(_sep: TerminalNode, _num: NonterminalNode) {
return (this as InterpreterNode).numberValue
},
SetMaxOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode): number {
return (maxvalue as InterpreterNode).maxValue;
},
SetMaxOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, maxvalue: NonterminalNode, _resource: NonterminalNode): number {
return (maxvalue as InterpreterNode).maxValue;
},
SetMaxOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, maxvalue: NonterminalNode): number {
return (maxvalue as InterpreterNode).maxValue;
},
MeteredValue(current: NonterminalNode, max: NonterminalNode): number {
return (max as InterpreterNode).maxValue
},
SetMeteredOperation(operands: NonterminalNode, space: NonterminalNode, resource: NonterminalNode, colon: NonterminalNode, value: NonterminalNode): number {
return (value as InterpreterNode).maxValue;
},
SetMeteredOperationAlternate(operands: NonterminalNode, colon: NonterminalNode, value: NonterminalNode, _resource: NonterminalNode): number {
return (value as InterpreterNode).maxValue;
},
SetMeteredOperationAlternate2(operands: NonterminalNode, colon: NonterminalNode, resource: NonterminalNode, value: NonterminalNode): number {
return (value as InterpreterNode).maxValue;
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's max value is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's max value is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's max value is`)
}
})
interpreter.addAttribute<FailReason>("failReason", {
avoid(_: TerminalNode): FailReason.Avoid {
return FailReason.Avoid
},
dodge(_: TerminalNode): FailReason.Dodge {
return FailReason.Dodge
},
miss(_: TerminalNode): FailReason.Miss {
return FailReason.Miss
},
resist(_: TerminalNode): FailReason.Resist {
return FailReason.Resist
},
fail(_: TerminalNode): FailReason.Fail {
return FailReason.Fail
},
block(_: TerminalNode): FailReason.Block {
return FailReason.Block
},
parry(_: TerminalNode): FailReason.Parry {
return FailReason.Parry
},
FailReason(reason: NonterminalNode): FailReason {
return (reason as InterpreterNode).failReason
},
FailOperation(operands: NonterminalNode, colon: NonterminalNode, reason: NonterminalNode): FailReason {
return (reason as InterpreterNode).failReason
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's fail reason is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's fail reason is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's fail reason is`)
}
})
interpreter.addAttribute<CharacterSide>("side", {
characterSide(side: NonterminalNode): CharacterSide {
return (side as InterpreterNode).side
},
enemySide(_: TerminalNode): CharacterSide.Enemy {
return CharacterSide.Enemy
},
allySide(_: TerminalNode): CharacterSide.Ally {
return CharacterSide.Ally
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's loyalties are`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's loyalties are`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's loyalties are`)
}
})
interpreter.addAttribute<CharacterPrivacy>("privacy", {
Privacy(level: NonterminalNode): CharacterPrivacy {
return (level as InterpreterNode).privacy
},
AllyPrivacy(_: TerminalNode): CharacterPrivacy.Friend {
return CharacterPrivacy.Friend
},
FullScanPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.FullyScannedEnemy {
return CharacterPrivacy.FullyScannedEnemy
},
ScanPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.ScannedEnemy {
return CharacterPrivacy.ScannedEnemy
},
LightScanPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.LightlyScannedEnemy {
return CharacterPrivacy.LightlyScannedEnemy
},
NotScannedPrivacy(_: NonterminalNode, __: NonterminalNode, ___: NonterminalNode): CharacterPrivacy.UnscannedEnemy {
return CharacterPrivacy.UnscannedEnemy
},
SecretPrivacy(_: TerminalNode): CharacterPrivacy.SecretiveEnemy {
return CharacterPrivacy.SecretiveEnemy
},
HiddenPrivacy(_: TerminalNode): CharacterPrivacy.Hidden {
return CharacterPrivacy.Hidden
},
_iter(): never {
throw Error(`No idea what to say ${this.ctorName} iteration node's privacy is`)
},
_nonterminal(): never {
throw Error(`No idea what to say ${this.ctorName} nonterminal node's privacy is`)
},
_terminal(): never {
throw Error(`No idea what to say ${this.ctorName} terminal node's privacy is`)
}
})
function EvaluateDelta(node: EvaluationNode): EvaluationContext {
const ctx = node.args.ctx
const operands = evaluateOperands(node.operands, ctx)
const resource = node.resource
const delta = node.numberValue
if (delta === 0 || operands.size === 0) {
return ctx
}
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterResources.applyDelta(c, resource ?? MeteredResource.Health, delta) : c),
clocks: ctx.game.clocks.map((c) =>
operands.has(c.id) ? ClockResources.applyDelta(c, resource ?? MeteredResource.Segments, delta) : c),
}
}
}
function EvaluateMeteredSet(node: EvaluationNode): EvaluationContext {
const ctx = node.args.ctx
const operands = evaluateOperands(node.operands, ctx)
const resource = node.resource
const current = node.currentValue
const max = node.maxValue
if (operands.size === 0) {
return ctx
}
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterResources.setMetered(c, resource ?? MeteredResource.Health, current, max) : c),
clocks: ctx.game.clocks.map((c) =>
operands.has(c.id) ? ClockResources.setMetered(c, resource ?? MeteredResource.Segments, current, max) : c),
}
}
}
function EvaluateValueSet(node: EvaluationNode): EvaluationContext {
const ctx = node.args.ctx
const operands = evaluateOperands(node.operands, ctx)
const resource = node.resource
const current = node.currentValue
if (operands.size === 0) {
return ctx
}
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterResources.setValue(c, resource ?? MeteredResource.Health, current) : c),
clocks: ctx.game.clocks.map((c) =>
operands.has(c.id) ? ClockResources.setValue(c, resource ?? MeteredResource.Segments, current) : c),
}
}
}
function EvaluateMaxSet(node: EvaluationNode): EvaluationContext {
const ctx = node.args.ctx
const operands = evaluateOperands(node.operands, ctx)
const resource = node.resource
const max = node.maxValue
if (operands.size === 0) {
return ctx
}
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterResources.setMax(c, resource ?? MeteredResource.Health, max) : c),
clocks: ctx.game.clocks.map((c) =>
operands.has(c.id) ? ClockResources.setMax(c, resource ?? MeteredResource.Segments, max) : c),
}
}
}
function EvaluateClear(node: EvaluationNode): EvaluationContext {
const ctx = node.args.ctx
const operands = evaluateOperands(node.operands, ctx)
const resource = node.resource
if (operands.size === 0 || !resource) {
return ctx
}
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterResources.clear(c, resource) : c),
clocks: ctx.game.clocks.map((c) =>
operands.has(c.id) ? ClockResources.clear(c, resource) : c),
}
}
}
interpreter.addOperation<EvaluationContext>("evaluate(ctx)", {
CodeSegment(items: IterationNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
return (items as InterpreterNode).evaluate(ctx)
},
EmptyLines(_: TerminalNode): EvaluationContext {
// Has no effect
return (this as EvaluationNode).args.ctx
},
Block(start: NonterminalNode, terminator: NonterminalNode, code: NonterminalNode,
_end: NonterminalNode, _finalTerminator: NonterminalNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
ctx = (start as InterpreterNode).evaluate(ctx)
ctx = (code as InterpreterNode).evaluate(ctx)
return ctx
},
BlockStart(silentOperator: IterationNode, beginKeyword: NonterminalNode,
source: IterationNode, target: IterationNode,
_title: IterationNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
ctx = (source as InterpreterNode).evaluate(ctx)
ctx = (target as InterpreterNode).evaluate(ctx)
return ctx
},
CompleteOperation(silenced: IterationNode, operation: NonterminalNode, _terminator: NonterminalNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
ctx = (operation as InterpreterNode).evaluate(ctx)
return ctx
},
Operation(operator: NonterminalNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
return (operator as InterpreterNode).evaluate(ctx)
},
DeltaOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode, _arg5: IterationNode, _arg6: IterationNode): EvaluationContext {
return EvaluateDelta(this as EvaluationNode)
},
DeltaOperationAlternate(_operators: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _res: IterationNode, _element: IterationNode): EvaluationContext {
return EvaluateDelta(this as EvaluationNode)
},
DeltaOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: IterationNode, _arg5: IterationNode): EvaluationContext {
return EvaluateDelta(this as EvaluationNode)
},
SetMeteredOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
return EvaluateMeteredSet(this as EvaluationNode)
},
SetMeteredOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateMeteredSet(this as EvaluationNode)
},
SetMeteredOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateMeteredSet(this as EvaluationNode)
},
SetValueOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
return EvaluateValueSet(this as EvaluationNode);
},
SetValueOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateValueSet(this as EvaluationNode);
},
SetValueOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateValueSet(this as EvaluationNode);
},
SetMaxOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
return EvaluateMaxSet(this as EvaluationNode);
},
SetMaxOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateMaxSet(this as EvaluationNode);
},
SetMaxOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateMaxSet(this as EvaluationNode);
},
StatusOrItemAddOperation(_operators: NonterminalNode, _colon: NonterminalNode, _sign: NonterminalNode, _identifier: NonterminalNode, _counter: IterationNode): EvaluationContext {
const ctx = (this as EvaluationNode).args.ctx
const operands = evaluateOperands((this as InterpreterNode).operands, ctx)
const statusOrItemId = (this as InterpreterNode).identifier
const stacks = (this as InterpreterNode).numberValue
return {
...ctx,
game: {
...ctx.game,
characters: ctx.game.characters.map((c) =>
operands.has(c.id) ? CharacterStatuses.addStatus(c, statusOrItemId, stacks) : c),
}
}
},
ClearOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): EvaluationContext {
return EvaluateClear(this as EvaluationNode);
},
ClearOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateClear(this as EvaluationNode);
},
ClearOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): EvaluationContext {
return EvaluateClear(this as EvaluationNode);
},
SetSourceOperation(_: TerminalNode, __: NonterminalNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
return {
...ctx,
source: [...evaluateOperands((this as InterpreterNode).operands, ctx)]
}
},
SetTargetOperation(_: TerminalNode, __: NonterminalNode): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
return {
...ctx,
target: [...evaluateOperands((this as InterpreterNode).operands, ctx)]
}
},
_iter(...children: readonly Node[]): EvaluationContext {
let ctx = (this as EvaluationNode).args.ctx
for (const child of children) {
ctx = (child as InterpreterNode).evaluate(ctx)
}
return ctx
},
_nonterminal(): never {
throw Error(`No idea how to evaluate ${this.ctorName} nonterminal node.`)
},
_terminal(): never {
throw Error(`No idea how to evaluate ${this.ctorName} terminal node.`)
}
})
function RenderDelta(node: MarkdownNode): MarkdownOutput {
const ctx = node.args.ctx
const result: string[] = []
const resource = node.resource
const delta = node.numberValue
const affinity = node.affinity
const element = node.element
const operandsBefore = lookupOperands(node.operands, ctx)
const ctxAfter = EvaluateDelta(node)
const operandsAfter = lookupOperands(node.operands, ctxAfter)
for (const [id, after] of operandsAfter) {
const before = operandsBefore.get(id)
if (!before) {
throw Error("Somehow an object appeared?")
}
switch (before.type) {
case "character":
if (after.type !== "character") {
throw Error(`character ${id} somehow changed type between calls?`)
}
const beforeChar = before.character
const afterChar = after.character
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
if (resource === MeteredResource.Zero && typeof afterChar.zp === "number" && typeof afterChar.maxZp === "number" && afterChar.maxZp > 0) {
result.push(`${charName}: [Zero Charge: _${(100 * afterChar.zp / afterChar.maxZp).toFixed(0)}%_]`)
} else {
result.push(`${charName}: [${formatResourceDelta(resource ?? MeteredResource.Health, affinity, delta)}]${
element !== null ? " " + element : ""}`)
}
switch (resource) {
case MeteredResource.Health:
case null:
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
result.push(`${charName}: [**${afterChar.health}**]`)
}
break
case MeteredResource.Zero:
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
result.push(`${charName}: [**Zero Power Ready**]`)
}
break
case MeteredResource.Experience:
const oldLevel = beforeChar.level
const newLevel = afterChar.level
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
const levelDelta = newLevel - oldLevel
if (levelDelta < 0) {
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
} else {
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
}
}
break
}
break
case "clock":
if (after.type !== "clock") {
throw Error(`clock ${id} somehow changed type between calls?`)
}
const afterClock = after.clock
const clockText = afterClock.text
result.push(`${clockText}: [${formatResourceDelta(resource ?? MeteredResource.Segments, affinity, delta)}]${
element !== null ? " " + element : ""}`)
break
default:
throw Error(`Don't know how to render delta on ${before.type} ${id}`)
}
}
return {
...ctxAfter,
output: result.length > 0 ? result.join("\n") : null
}
}
function RenderMeteredSet(node: MarkdownNode): MarkdownOutput {
const ctx = node.args.ctx
const result: string[] = []
const resource = node.resource
const newValue = node.currentValue
const newMax = node.maxValue
const operandsBefore = lookupOperands(node.operands, ctx)
const ctxAfter = EvaluateDelta(node)
const operandsAfter = lookupOperands(node.operands, ctxAfter)
for (const [id, after] of operandsAfter) {
const before = operandsBefore.get(id)
if (!before) {
throw Error("Somehow an object appeared?")
}
switch (before.type) {
case "character":
if (after.type !== "character") {
throw Error(`character ${id} somehow changed type between calls?`)
}
if (resource && !isMeteredResource(resource)) {
throw Error(`somehow got non-metered resource in metered set`)
}
const beforeChar = before.character
const afterChar = after.character
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
if (resource === MeteredResource.Zero && newMax > 0) {
result.push(`${charName}: [Zero Charge: _${(100 * newValue / newMax).toFixed(0)}%_]`)
} else {
result.push(`${charName}: [${formatMeteredResource(resource ?? MeteredResource.Health, newValue, newMax)}]`)
}
switch (resource) {
case MeteredResource.Health:
case null:
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
result.push(`${charName}: [**${afterChar.health}**]`)
}
break
case MeteredResource.Zero:
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
result.push(`${charName}: [**Zero Power Ready**]`)
}
break
case MeteredResource.Experience:
const oldLevel = beforeChar.level
const newLevel = afterChar.level
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
const levelDelta = newLevel - oldLevel
if (levelDelta < 0) {
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
} else {
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
}
}
break
}
break
case "clock":
if (after.type !== "clock") {
throw Error(`clock ${id} somehow changed type between calls?`)
}
const afterClock = after.clock
const clockText = afterClock.text
if (resource && !isMeteredResource(resource)) {
throw Error(`somehow got non-metered resource in metered set`)
}
result.push(`${clockText}: [${formatMeteredResource(resource ?? MeteredResource.Segments, newValue, newMax)}`)
break
default:
throw Error(`Don't know how to render metered set on ${before.type} ${id}`)
}
}
return {
...ctxAfter,
output: result.length > 0 ? result.join("\n") : null
}
}
function RenderValueSet(node: MarkdownNode): MarkdownOutput {
const ctx = node.args.ctx
const result: string[] = []
const resource = node.resource
const newValue = node.currentValue
const operandsBefore = lookupOperands(node.operands, ctx)
const ctxAfter = EvaluateDelta(node)
const operandsAfter = lookupOperands(node.operands, ctxAfter)
for (const [id, after] of operandsAfter) {
const before = operandsBefore.get(id)
if (!before) {
throw Error("Somehow an object appeared?")
}
switch (before.type) {
case "character":
if (after.type !== "character") {
throw Error(`character ${id} somehow changed type between calls?`)
}
const beforeChar = before.character
const afterChar = after.character
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
if (resource === MeteredResource.Zero && isDefined(afterChar.maxZp) && afterChar.maxZp > 0) {
result.push(`${charName}: [Zero Charge: _${(100 * newValue / afterChar.maxZp).toFixed(0)}%_]`)
} else {
result.push(`${charName}: [${formatResourceValue(resource ?? MeteredResource.Health, newValue)}]`)
}
switch (resource) {
case MeteredResource.Health:
case null:
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
result.push(`${charName}: [**${afterChar.health}**]`)
}
break
case MeteredResource.Zero:
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
result.push(`${charName}: [**Zero Power Ready**]`)
}
break
case MeteredResource.Experience:
const oldLevel = beforeChar.level
const newLevel = afterChar.level
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
const levelDelta = newLevel - oldLevel
if (levelDelta < 0) {
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
} else {
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
}
}
break
}
break
case "clock":
if (after.type !== "clock") {
throw Error(`clock ${id} somehow changed type between calls?`)
}
const afterClock = after.clock
const clockText = afterClock.text
result.push(`${clockText}: [${formatResourceValue(resource ?? MeteredResource.Segments, newValue)}`)
break
default:
throw Error(`Don't know how to render metered set on ${before.type} ${id}`)
}
}
return {
...ctxAfter,
output: result.length > 0 ? result.join("\n") : null
}
}
function RenderMaxSet(node: MarkdownNode): MarkdownOutput {
const ctx = node.args.ctx
const result: string[] = []
const resource = node.resource
const newMax = node.maxValue
const operandsBefore = lookupOperands(node.operands, ctx)
const ctxAfter = EvaluateDelta(node)
const operandsAfter = lookupOperands(node.operands, ctxAfter)
for (const [id, after] of operandsAfter) {
const before = operandsBefore.get(id)
if (!before) {
throw Error("Somehow an object appeared?")
}
switch (before.type) {
case "character":
if (after.type !== "character") {
throw Error(`character ${id} somehow changed type between calls?`)
}
if (resource && !isMeteredResource(resource)) {
throw Error(`somehow got non-metered resource in metered set`)
}
const beforeChar = before.character
const afterChar = after.character
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
if (resource === MeteredResource.Zero && isDefined(afterChar.zp)) {
result.push(`${charName}: [Zero Charge: _${(100 * afterChar.zp / newMax).toFixed(0)}%_]`)
} else {
result.push(`${charName}: [${formatResourceMax(resource ?? MeteredResource.Health, newMax)}]`)
}
switch (resource) {
case MeteredResource.Health:
case null:
if (beforeChar.health !== afterChar.health && typeof afterChar.health !== "undefined") {
result.push(`${charName}: [**${afterChar.health}**]`)
}
break
case MeteredResource.Zero:
if (isZpReady(beforeChar) === false && isZpReady(afterChar) === true) {
result.push(`${charName}: [**Zero Power Ready**]`)
}
break
case MeteredResource.Experience:
const oldLevel = beforeChar.level
const newLevel = afterChar.level
if (typeof oldLevel === "number" && typeof newLevel === "number" && oldLevel !== newLevel) {
const levelDelta = newLevel - oldLevel
if (levelDelta < 0) {
result.push(`${charName}: [_Level Down...${levelDelta < -1 ? ` x${-levelDelta}` : ""}_]`)
} else {
result.push(`${charName}: [**Level Up!!${levelDelta > 1 ? ` x${levelDelta}` : ""}**]`)
}
}
break
}
break
case "clock":
if (after.type !== "clock") {
throw Error(`clock ${id} somehow changed type between calls?`)
}
const afterClock = after.clock
const clockText = afterClock.text
if (resource && !isMeteredResource(resource)) {
throw Error(`somehow got non-metered resource in metered set`)
}
result.push(`${clockText}: [${formatResourceMax(resource ?? MeteredResource.Segments, newMax)}`)
break
default:
throw Error(`Don't know how to render metered set on ${before.type} ${id}`)
}
}
return {
...ctxAfter,
output: result.length > 0 ? result.join("\n") : null
}
}
function RenderClear(node: MarkdownNode): MarkdownOutput {
const ctx = node.args.ctx
const result: string[] = []
const resource = node.resource
const newMax = node.maxValue
const operandsBefore = lookupOperands(node.operands, ctx)
const ctxAfter = EvaluateDelta(node)
const operandsAfter = lookupOperands(node.operands, ctxAfter)
for (const [id, after] of operandsAfter) {
const before = operandsBefore.get(id)
if (!before) {
throw Error("Somehow an object appeared?")
}
switch (before.type) {
case "character":
if (after.type !== "character") {
throw Error(`character ${id} somehow changed type between calls?`)
}
const afterChar = after.character
const charName = (afterChar.privacy ? applyCharacterPrivacy(afterChar) : afterChar)?.name
result.push(`${charName}: [${resource} -]`)
break
case "clock":
if (after.type !== "clock") {
throw Error(`clock ${id} somehow changed type between calls?`)
}
const afterClock = after.clock
const clockText = afterClock.text
if (resource && !isMeteredResource(resource)) {
throw Error(`somehow got non-metered resource in metered set`)
}
result.push(`${clockText}: [${formatResourceMax(resource ?? MeteredResource.Segments, newMax)}`)
break
default:
throw Error(`Don't know how to render metered set on ${before.type} ${id}`)
}
}
return {
...ctxAfter,
output: result.length > 0 ? result.join("\n") : null
}
}
interpreter.addOperation<MarkdownOutput>("renderMarkdown(ctx)", {
CodeSegment(items: IterationNode): MarkdownOutput {
let ctx = (this as MarkdownNode).args.ctx
return (items as InterpreterNode).renderMarkdown(ctx)
},
EmptyLines(_items: IterationNode): MarkdownOutput {
let ctx = (this as MarkdownNode).args.ctx
return {...ctx, output: null}
},
DeltaOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode, _arg5: IterationNode, _arg6: IterationNode): MarkdownOutput {
return RenderDelta(this as MarkdownNode)
},
DeltaOperationAlternate(_operators: NonterminalNode, _colon: NonterminalNode, _delta: NonterminalNode, _affinity: IterationNode, _res: IterationNode, _element: IterationNode): MarkdownOutput {
return RenderDelta(this as MarkdownNode)
},
DeltaOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: IterationNode, _arg5: IterationNode): MarkdownOutput {
return RenderDelta(this as MarkdownNode)
},
SetMeteredOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
return RenderMeteredSet(this as MarkdownNode)
},
SetMeteredOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderMeteredSet(this as MarkdownNode)
},
SetMeteredOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderMeteredSet(this as MarkdownNode)
},
SetValueOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
return RenderValueSet(this as MarkdownNode);
},
SetValueOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderValueSet(this as MarkdownNode);
},
SetValueOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderValueSet(this as MarkdownNode);
},
SetMaxOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
return RenderMaxSet(this as MarkdownNode);
},
SetMaxOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderMaxSet(this as MarkdownNode);
},
SetMaxOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderMaxSet(this as MarkdownNode);
},
ClearOperation(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode, _arg4: NonterminalNode): MarkdownOutput {
return RenderClear(this as MarkdownNode);
},
ClearOperationAlternate(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderClear(this as MarkdownNode);
},
ClearOperationAlternate2(_arg0: NonterminalNode, _arg1: NonterminalNode, _arg2: NonterminalNode, _arg3: NonterminalNode): MarkdownOutput {
return RenderClear(this as MarkdownNode);
},
_iter(...children: readonly Node[]): MarkdownOutput {
let ctx = (this as MarkdownNode).args.ctx
const output = []
for (const child of children) {
const result = (child as InterpreterNode).renderMarkdown(ctx)
if (result.output !== null) {
output.push(result.output)
}
ctx = result
}
return {
...ctx,
output: output.join('\n')
}
},
_nonterminal(): never {
throw Error(`No idea how to evaluate ${this.ctorName} nonterminal node for markdown.`)
},
_terminal(): never {
throw Error(`No idea how to evaluate ${this.ctorName} terminal node for markdown.`)
}
})
export function evaluate(state: GameState, timestamp: number, code: string): GameState {
const codeMatch = grammar.match(code, "CodeSegment")
const context: EvaluationContext = {
timestamp,
game: state,
target: [],
source: [],
}
const result = (interpreter(codeMatch) as InterpreterNode).evaluate(context)
return result.game
}