diff --git a/libs/sr-formula/src/data/common/index.ts b/libs/sr-formula/src/data/common/index.ts index 1567277146..0592655e46 100644 --- a/libs/sr-formula/src/data/common/index.ts +++ b/libs/sr-formula/src/data/common/index.ts @@ -11,10 +11,10 @@ const data: TagMapNodeEntries = [ reader.withTag({ src: 'iso', et: 'self' }).reread(reader.src('custom')), reader.withTag({ src: 'agg', et: 'self' }).reread(reader.src('custom')), - // convert src:char, lightCone, relic to src:agg for accumulation - reader.src('agg').add(reader.sum.src('char')), - reader.src('agg').add(reader.sum.src('lightCone')), - reader.src('agg').add(reader.sum.src('relic')), + // convert src:char, lightCone to src:agg for accumulation + // src: relic is reread in src/util.ts:relicsData() + reader.src('agg').reread(reader.src('char')), + reader.src('agg').reread(reader.src('lightCone')), // Final <= Premod <= Base reader diff --git a/libs/sr-formula/src/data/util/read.ts b/libs/sr-formula/src/data/util/read.ts index 7c25109f57..02cf979714 100644 --- a/libs/sr-formula/src/data/util/read.ts +++ b/libs/sr-formula/src/data/util/read.ts @@ -25,7 +25,7 @@ import { } from './listing' export const fixedTags = { - presets: presets, + preset: presets, member: members, dst: members, et: entryTypes, @@ -117,6 +117,55 @@ export function tagVal(cat: keyof Tag): TagValRead { return baseTagVal(cat) } +export function tagStr(tag: Tag, ex?: any): string { + const { + name, + preset, + member, + dst, + et, + src, + q, + qt, + type, + attackType, + ...remaining + } = tag + + if (Object.keys(remaining).length) console.error(remaining) + + let result = '{ ', + includedRequired = false, + includedBar = false + function required(str: string | undefined | null) { + if (!str) return + result += str + ' ' + includedRequired = true + } + function optional(str: string | undefined | null) { + if (!str) return + if (includedRequired && !includedBar) { + includedBar = true + result += '| ' + } + result += str + ' ' + } + required(name && `#${name}`) + required(preset) + required(member) + required(dst && `(${dst})`) + required(src) + required(et) + if (qt && q) required(`${qt}.${q}`) + else if (qt) required(`${qt}.`) + else if (q) required(`.${q}`) + + optional(type) + optional(attackType) + required(ex && `[${ex}]`) + return result + '}' +} + export const reader = new Read({}, undefined) export const usedNames = new Set() export const usedQ = new Set('_') diff --git a/libs/sr-formula/src/debug.ts b/libs/sr-formula/src/debug.ts index 1202b21098..9a88c8ed4c 100644 --- a/libs/sr-formula/src/debug.ts +++ b/libs/sr-formula/src/debug.ts @@ -1,11 +1,21 @@ import type { AnyNode, + CalcResult, ReRead, TagMapSubsetCache, } from '@genshin-optimizer/pando' -import { TagMapExactValues, traverse } from '@genshin-optimizer/pando' -import type { Read, Tag } from './data/util' -import type { Calculator } from './calculator' +import { + Calculator as BaseCalculator, + TagMapExactValues, + TagMapKeys, + traverse, +} from '@genshin-optimizer/pando' +import { type Calculator } from './calculator' +import { keys } from './data' +import { tagStr, type Read, type Tag, type TagMapNodeEntry } from './data/util' + +const tagKeys = new TagMapKeys(keys) +export const debugKey = Symbol('tagSource') export function dependencyString(read: Read, calc: Calculator) { const str = listDependencies(read.tag, calc).map(({ tag, read, reread }) => { @@ -30,8 +40,7 @@ export function listDependencies( const result: { tag: Tag; read: Tag[]; reread: Tag[] }[] = [], stack: Tag[] = [] /** Stack depth when first encountered the tag, or 0 if already visited */ - const tagKeys = calc.keys - const openDepth = new TagMapExactValues(tagKeys.tagLen, {}) + const openDepth = new TagMapExactValues(keys.tagLen, {}) function internal(cache: TagMapSubsetCache) { const tag = cache.tag, @@ -81,3 +90,132 @@ export function listDependencies( internal(calc.nodes.cache(calc.keys).with(tag)) return result } + +export function printEntry({ tag, value }: TagMapNodeEntry): string { + function printNode(node: AnyNode): string { + const { op, tag, br, x } = node + let { ex } = node + if (op === 'const') return JSON.stringify(ex) + if (op === 'read') return `${node}` + if (op === 'subscript') ex = undefined + const args: string[] = [] + if (ex) args.push(JSON.stringify(ex)) + if (tag) args.push(tagStr(tag)) + args.push(...br.map(printNode), ...x.map(printNode)) + return `${op}(` + args.join(', ') + ')' + } + + if (value.op === 'reread') return tagStr(tag) + ` <- ` + tagStr(value.tag) + return tagStr(tag) + ` <= ` + printNode(value) +} + +export type DebugMeta = { + valText: string + text: string + deps: DebugMeta[] +} +export class DebugCalculator extends BaseCalculator { + override computeMeta( + n: AnyNode, + val: number | string, + x: (CalcResult | undefined)[], + br: CalcResult[], + tag: Tag | undefined + ): DebugMeta { + const result: DebugMeta = { + valText: valStr(val), + text: '', + deps: [], + } + function toStr( + x: CalcResult | undefined + ): string { + if (!x) return '' + result.deps.push(...x.meta.deps) + return x.meta.text + } + + function valStr(val: number | string): string { + if (typeof val !== 'number') return `"${val}"` + if (Math.round(val) === val) return `${val}` + return val.toFixed(2) + } + + const { op, ex, tag: nTag } = n + switch (op) { + case 'const': + result.text = result.valText + break + case 'read': { + const args = x as CalcResult[] + return { + valText: result.valText, + text: tagStr(nTag!, ex), + deps: [ + { + valText: result.valText, + text: `expand ${tagStr(nTag!, ex)} (${tagStr(tag!)})`, + deps: args.map(({ meta, entryTag }) => ({ + ...meta, + text: `${entryTag?.map((tag) => tagStr(tag)).join(' <- ')} <= ${ + meta.text + }`, + })), + }, + ], + } + } + case 'match': + case 'thres': + case 'lookup': { + const chosen = x.find((x) => x)! + result.text = `${op}(${br.map(toStr).join(', ')} => ${toStr(chosen)})` + break + } + case 'subscript': { + const [index] = br + const chosen = valStr(ex[index.val as number]!) + result.text = `subscript(${toStr(index)} => ${chosen})` + break + } + case 'tag': + result.text = `tag(${tagStr(nTag!)}, ${toStr(x[0])})` + break + case 'dtag': + result.text = `dtag(${br + .map((br, i) => `${ex[i]} => ${toStr(br)}`) + .join(', ')}; ${toStr(x[0])})` + break + default: { + const specialArgs = ex ? [JSON.stringify(ex)] : [] + const brArgs = br.map(toStr) + const xArgs = x.map(toStr) + const args = [specialArgs, brArgs, xArgs].map((x) => x.join(', ')) + + result.text = `${op}(` + args.filter((x) => x.length).join('; ') + ')' + } + } + return result + } + + debug(node: AnyNode): string[] { + const { meta } = this.compute(node) + const result: string[] = [] + const found = new Set() + + function print(meta: DebugMeta, level: number) { + const indent = Array(2 * level + 1).join(' ') + const line = `${meta.valText} ${meta.text}` + + if (found.has(meta)) { + result.push(indent + line + ' (Dup)') + } else { + found.add(meta) + result.push(indent + line) + meta.deps.forEach((dep) => print(dep, level + 1)) + } + } + print(meta, 0) + return result + } +} diff --git a/libs/sr-formula/src/index.ts b/libs/sr-formula/src/index.ts index 278dbb08c0..a68e0aa4c9 100644 --- a/libs/sr-formula/src/index.ts +++ b/libs/sr-formula/src/index.ts @@ -4,6 +4,7 @@ import { Calculator } from './calculator' import { keys, values } from './data' export { Calculator } from './calculator' export * from './data/util' +export * from './debug' export * from './util' export function srCalculatorWithValues(extras: TagMapEntries) {