Files
Aislo/resources/tester/helper_804_2_line_stack.cjs
T
eomsangdonandClaude Opus 5.5 af576e5a7a feat(804-5): 줄 쌓기 왼쪽 손질 — 자원 이름 단추 · 항목 띠 · 끌어 옮기기 · X 지움 되돌리기 · 율 번지 읽는 이름
- 인력 · 기계 · 재료 · 단가산출 줄 자원 이름 = 단추(이름 · 규격) → 찾아 넣기 창 → 바꿈 · [찾아 바꾸기] 걷음
- 항목 머리 = 띠(바탕 · 왼쪽 굵은 띠 · 번호) · 줄 = 들여쓰기 + 왼쪽 세로 선 · 가나다 한 단 더 · 접기
- 순서 = 손잡이 끌기(줄 = 항목 안 · 사이 · 항목 끌기 · 자리 선 표시) · [↑][↓] 걷음
- 지움 = X 아이콘 · 확인 없이 지우고 알림 눌러 되돌리기(그 뒤 고침 있으면 못 함)
- 산출근거 율 줄 「번지.복사N.노무비」 → 그 조각 이름 · 값 식 = 번지 값 · 합계 덧셈 항 넷 넘으면 항목 소계끼리
- 알림 부품: 누름 동작 알림은 같은 문구 버림 안 함(두 번째 지움 되돌리기 안 뜨던 흠)
- 글 · 근거 카드 높이 늘던 흠 고침 · 시험 test_804_4 끌어옮김 · 번지 이름 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 08:29:31 +09:00

229 lines
9.2 KiB
JavaScript

/* 804-2 줄 쌓기 헬퍼 — common_util_line_stack.ts · 계산기 · 캐시를 그 자리에서 트랜스파일해 Node 로 돌림.
* stdin = {할일, 문서들 [{문서, 연결?}]} · stdout = 할일마다 답 목록 */
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const cache = new Map();
function load(at) {
if (cache.has(at)) return cache.get(at);
const js = ts.transpileModule(fs.readFileSync(at, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(at, box.exports);
const need = (p) =>
p.startsWith(".") ? load(path.resolve(path.dirname(at), `${p}.ts`)) : require(p);
new Function("exports", "module", "require", js)(box.exports, box, need);
return box.exports;
}
const util = (name) => load(path.join(__dirname, "..", "..", "common_util", `${name}.ts`));
const ls = util("common_util_line_stack");
const calc = util("common_util_formula_doc_calc");
const dc = util("common_util_formula_doc_cache");
const money = (a) => ({ 노무비: a.노무비, 재료비: a.재료비, 경비: a.경비, 계: a.계, 결과: a.결과 });
/** 왕복 — 문서 → 줄 → 문서 · 덮음(모든 금액 줄 한 번 · 차림 기호 한 번) · 차림 고침 뒤 다시 읽어 같음 */
function roundTrip({ 문서 }) {
const items = ls.stackOf(문서);
const back = ls.docOf(문서, items);
const ms = items.flatMap((it) => it.줄.filter((l) => l.꼴 === "금액").map((l) => l.i));
const syms = items.flatMap((it) => it.줄.filter((l) => l.꼴 === "공식").flatMap((l) => l.기호));
const hidden = ls.hiddenOf(문서);
const want = ls.symbols(문서).filter((x) => !hidden.has(x));
// 차림 고침 — 항목마다 글 줄 · 첫 항목 가나다 · 맨 앞 새 항목 제목
const edited = JSON.parse(JSON.stringify(items));
edited.forEach((it, k) => it.줄.push({ 꼴: "글", 글: `< 근거 ${k} >` }));
if (edited[0]) edited[0].가나다 = true;
edited.unshift({ 제목: "시험 머리", 줄: [{ 꼴: "글", 글: "※ 메모" }] });
const laid = ls.docOf(문서, edited);
const again = ls.stackOf(laid);
return {
같음: JSON.stringify(back) === JSON.stringify(문서),
쌓기: "쌓기" in back,
금액: ms.length === (문서.금액 || []).length && new Set(ms).size === ms.length,
기호:
syms.length === want.length &&
new Set(syms).size === syms.length &&
want.every((x) => syms.includes(x)),
차림: JSON.stringify(again) === JSON.stringify(edited),
차림문서: laid,
못: ls.notesOf(문서),
};
}
/** 셈 — 연결 기록만으로(화면과 같은 길) · 기계 · 단가산출 펼침 셈 */
function money_({ 문서, 연결 }) {
const c = dc.createDocCache();
c.merge(연결);
const a = calc.calcDoc(문서, c.links, {});
const subs = [];
(문서.금액 || []).forEach((l, i) => {
const kind = ls.kindOf(문서, i);
if (kind !== "기계" && kind !== "단가산출") return;
const s = ls.priceSym(l);
const link = (문서.변수 || []).find((v) => ls.symbols({ 변수: [v] })[0] === s)?.연결;
const sub = c.links.doc(link.키);
const ins = ls.subInputs(link, a.값 || {});
const got = calc.calcDoc(sub, c.links, ins);
subs.push({ i, 갈래: kind, 키: link.키, 입력: ins, ...money(got), 값: (a.값 || {})[s] });
});
return { ...money(a), 펼침: subs, 누락: c.takeMissing() };
}
/** 틀 펼침 — 단가산출 결과 연결 기호를 이 문서 식으로 옮김 → 셈 같음 · 기호 없으면 그런 기호 전부(하나씩)
* 고침 = 펼친 뒤 [{기호, 글}](setRow) · 지움 = 펼친 뒤 지울 기호(dropSyms) */
function unfold({ 문서, 연결, 기호, 고침, 지움 }) {
const c = dc.createDocCache();
c.merge(연결);
const before = calc.calcDoc(문서, c.links, {});
const idOf = (v) => ls.symbols({ 변수: [v] })[0];
const targets = 기호
? [기호]
: (문서.변수 || [])
.filter((v) => v.연결?.갈래 === "단가산출" && v.연결.키 && !v.연결.키.includes("{"))
.filter((v) => c.links.doc(v.연결.키)?.결과)
.map(idOf);
return targets.map((id) => {
const link = 문서.변수.find((v) => idOf(v) === id).연결;
const doc = JSON.parse(JSON.stringify(문서));
const moved = ls.unfold(doc, c.links.doc(link.키), id);
const after = calc.calcDoc(doc, c.links, {});
for (const g of 고침 || []) ls.setRow(doc, g.기호, g.글);
if (지움) ls.dropSyms(doc, 지움);
const edited = calc.calcDoc(doc, c.links, {});
const items = ls.stackOf(doc);
return {
기호: id,
전: money(before),
후: money(after),
고친: money(edited),
표지: (edited.표지 || []).map((m) => m.갈래),
옮김: moved,
틀꼴: ls.shapeOf(doc, moved),
왕복: JSON.stringify(ls.docOf(doc, items)) === JSON.stringify(doc),
문서: doc,
};
});
}
/* 804-3 고르기 — common_util_line_stack_pick.ts */
const pk = util("common_util_line_stack_pick");
const idOf = (v) => ls.symbols({ 변수: [v] })[0];
const varOf = (doc, id) => (doc.변수 || []).find((v) => idOf(v) === id);
/** 표 고름 — 앞 = [{기호, 조건}](먼저 조건을 바꿔 둠) · 기호의 표에서 줄 · 칸 고름 → 연결 · 값 · 금액 */
function tablePick({ 문서, 연결, 기호, 줄, 칸, 앞 }) {
const doc = JSON.parse(JSON.stringify(문서));
for (const f of 앞 || []) varOf(doc, f.기호).연결.조건 = f.조건;
const c = dc.createDocCache();
c.merge(연결);
const link = varOf(doc, 기호).연결;
const t = 연결.표[link.표];
const before = calc.calcDoc(doc, c.links, {});
const now = pk.currentRow(t, link, before.값 || {});
const bound = pk.boundConds(doc, link);
pk.pickCell(doc, 기호, t, t.줄[줄], 칸);
const after = calc.calcDoc(doc, c.links, {});
return {
전줄: now,
묶임: bound,
연결: varOf(doc, 기호).연결,
값: (after.값 || {})[기호],
후줄: pk.currentRow(t, varOf(doc, 기호).연결, after.값 || {}),
...money(after),
문서: doc,
};
}
/** 찾아 넣기 — 새 항목에 고른 것 차례로 · 바꿈 = [{차례, 고른}](넣은 줄 다시 고름) */
function putPicked({ 문서, 넣을, 바꿈 }) {
const doc = JSON.parse(JSON.stringify(문서));
const items = ls.stackOf(doc);
items.push({ 제목: "시험 넣기", 가나다: true, 줄: [] });
const base = (doc.금액 || []).length;
const ids = 넣을.map((x) => pk.addPicked(doc, items[items.length - 1], x.갈래, x.고른));
for (const r of 바꿈 || []) pk.repick(doc, base + r.차례, ids[r.차례], 넣을[r.차례].갈래, r.고른);
const laid = ls.docOf(doc, items);
return {
문서: laid,
기호: ids,
처음: base,
갈래: ids.map((_, k) => ls.kindOf(laid, base + k)),
수준: pk.stackLevels(ls.stackOf(laid)),
};
}
/** 있는 줄 다시 고름 — {문서, 줄, 기호, 갈래, 고른} → 문서 */
function repickOne({ 문서, 줄, 기호, 갈래, 고른 }) {
const doc = JSON.parse(JSON.stringify(문서));
pk.repick(doc, 줄, 기호, 갈래, 고른);
return doc;
}
/* 804-4 복사해 펼침 — common_util_line_stack_copy.ts · 받음 = 서버 조각(차례로 넣음) */
const cp = util("common_util_line_stack_copy");
function takeIn({ 문서, 받음 }) {
const doc = JSON.parse(JSON.stringify(문서));
const items = ls.stackOf(doc);
const was = items.length;
for (const t of 받음) cp.addTaken(doc, items, t);
const laid = ls.docOf(doc, items);
const again = ls.stackOf(laid);
return {
문서: laid,
항목: again.slice(was).map((it) => ({
제목: it.제목,
가나다: !!it.가나다,
줄: it.줄.map((l) =>
l.꼴 === "금액"
? `금액:${laid.금액[l.i].이름}`
: l.꼴 === "글"
? `글:${l.글}`
: `공식:${l.기호.length}`,
),
})),
옵션: (laid.변수 || []).filter((v) => ls.isOption(v)).map(idOf),
못: ls.notesOf(laid),
왕복: JSON.stringify(ls.docOf(laid, again)) === JSON.stringify(laid),
};
}
/* 804-5 끌어 옮김 · 번지 읽는 이름 — 옮김 = [[from, to] …](줄 "i.j" → "k.p" · 항목 "i" → "p") */
function moveAll({ 문서, 옮김 }) {
let doc = JSON.parse(JSON.stringify(문서));
for (const [a, b] of 옮김) {
const items = ls.stackOf(doc);
if (a.includes("."))
ls.moveLine(items, ...a.split(".").map(Number), ...b.split(".").map(Number));
else ls.moveItem(items, Number(a), Number(b));
doc = ls.docOf(doc, items);
}
const items = ls.stackOf(doc);
return {
문서: doc,
항목: items.map((it) => ({
제목: it.제목,
줄: it.줄.map((l) =>
l.꼴 === "금액" ? doc.금액[l.i].이름 : l.꼴 === "글" ? l.글 : l.기호.join(","),
),
})),
};
}
const placeOf = ({ 문서 }) => cp.placeNames(문서, ls.stackOf(문서));
const input = JSON.parse(fs.readFileSync(0, "utf8"));
const job = {
왕복: roundTrip,
셈: money_,
펼침: unfold,
표고름: tablePick,
넣기: putPicked,
바꿈: repickOne,
복사: takeIn,
옮김: moveAll,
번지: placeOf,
}[input.할일];
process.stdout.write(JSON.stringify(input.문서들.map(job)));