// 칩 계층. chipdef 블록을 칩 정의로, chip 줄을 인스턴스로 읽고, 평가할 때는 칩 경계를 지워 원시 게이트 회로로 펼칩니다.
// 입력은 node, wire, chip, chipdef 이름 ... endchip, 칩 안의 extin 핀 노드와 extout 핀 노드 줄이고 출력은 분해 결과나 첫 오류입니다.
// 같은 입력이면 늘 같은 분해와 같은 평탄 회로가 나옵니다. 칩의 진리는 펼친 회로의 진리와 같습니다.
// 그리거나 직렬화하지 않습니다. 칩은 앞에서 정의한 칩만 쓸 수 있어 정의 순환을 받지 않습니다.
// Chip hierarchy: parse chip definitions and instances, flatten them to primitive gates and evaluate nested views.

import core.netlist
import core.sim
import core.fault
import core.util

export type 외부핀 = { 핀: string, 노드: string }
export type 인스턴스 = { 아이디: string, 칩이름: string, x: int, y: int }
export type 끝점 = { 노드: string, 핀: string }
// 표들은 전선과 핀을 찾을 때마다 배열을 훑지 않도록 한 번 만든 색인입니다. 같은 키가 여럿이면 배열의 마지막 것이 남습니다.
// 구동표는 끝 노드:끝 핀에서 그 핀을 구동하는 시작 끝점으로 갑니다. 전선 끝점에는 : 가 없어 키가 겹치지 않습니다.
export type 회로본체 = { 노드들: Array<netlist.노드>, 전선들: Array<netlist.전선>, 인스턴스들: Array<인스턴스>, 구동표: Map<string, 끝점> }
// 본체는 펼치기 전 구조이고 내부는 펼친 원시 회로입니다. 내보내기와 칩 안 보기는 본체를 씁니다.
// 외부입력표는 내부 노드에서 extin 핀으로, 외부출력표는 extout 핀에서 내부 노드로, 터미널구동표는 내부 끝 노드에서 그 노드를 구동하는 끝점으로 갑니다.
export type 칩정의 = {
  이름: string,
  본체: 회로본체,
  내부: netlist.회로,
  외부입력: Array<외부핀>,
  외부출력: Array<외부핀>,
  외부입력표: Map<string, string>,
  외부출력표: Map<string, string>,
  출력터미널들: Set<string>,
  터미널구동표: Map<string, 끝점>,
}
export type 분해결과 = { 정의들: Array<칩정의>, 정의표: Map<string, 칩정의>, 본체: 회로본체 }
export type 놓인칩 = { 배치: 인스턴스, 정의: 칩정의 }
export type 보기단계 = { 분해: 분해결과, 시뮬: sim.시뮬결과, 고장들: Array<fault.고장> }
export type 경로보기 = { 단계: 보기단계, 이름들: Array<string> }

type 줄머리 = "chipdef" | "endchip" | "node" | "wire" | "chip" | "extin" | "extout" | "기타"
type 핀쪽 = "입력" | "출력"
enum 본체항목 { 노드줄(Array<string>), 전선줄(netlist.전선), 칩줄(인스턴스), 외부입력줄(외부핀), 외부출력줄(외부핀) }
// node 줄은 앞에서 정의한 칩이 모두 모인 뒤 netlist 가 검증하므로 토큰으로 둡니다.
type 생본체 = {
  노드줄들: Array<Array<string>>,
  전선들: Array<netlist.전선>,
  인스턴스들: Array<인스턴스>,
  외부입력: Array<외부핀>,
  외부출력: Array<외부핀>,
}
type 생칩정의 = { 이름: string, 본체: 생본체 }

let 빈끝점: 끝점 = { 노드: "", 핀: "" }
let 빈정의: 칩정의 = {
  이름: "",
  본체: { 노드들: [], 전선들: [], 인스턴스들: [], 구동표: Map.new() },
  내부: { 노드들: [], 전선들: [] },
  외부입력: [],
  외부출력: [],
  외부입력표: Map.new(),
  외부출력표: Map.new(),
  출력터미널들: Set.of(),
  터미널구동표: Map.new(),
}

function 이름공간(칩아이디: string, 노드아이디: string) -> string { 칩아이디 + "__" + 노드아이디 }

function 구동표만들기(전선들: Array<netlist.전선>) -> Map<string, 끝점> {
  let mut 표: Map<string, 끝점> = Map.new()
  for 선 in 전선들 { 표.insert(선.끝노드 + ":" + 선.끝핀, { 노드: 선.시작노드, 핀: 선.시작핀 }) }
  표
}

// extout 터미널은 OUTPUT 이라 in0 하나만 받으므로 끝 노드만 키로 씁니다.
function 터미널구동표만들기(전선들: Array<netlist.전선>) -> Map<string, 끝점> {
  let mut 표: Map<string, 끝점> = Map.new()
  for 선 in 전선들 { 표.insert(선.끝노드, { 노드: 선.시작노드, 핀: 선.시작핀 }) }
  표
}

function 노드에서핀표(핀들: Array<외부핀>) -> Map<string, string> {
  let mut 표: Map<string, string> = Map.new()
  for 핀값 in 핀들 { 표.insert(핀값.노드, 핀값.핀) }
  표
}

function 핀에서노드표(핀들: Array<외부핀>) -> Map<string, string> {
  let mut 표: Map<string, string> = Map.new()
  for 핀값 in 핀들 { 표.insert(핀값.핀, 핀값.노드) }
  표
}

function 정의표만들기(정의들: Array<칩정의>) -> Map<string, 칩정의> {
  let mut 표: Map<string, 칩정의> = Map.new()
  for 정의 in 정의들 { 표.insert(정의.이름, 정의) }
  표
}

function 분해결과만들기(정의들: Array<칩정의>, 본체값: 회로본체) -> 분해결과 {
  { 정의들: 정의들, 정의표: 정의표만들기(정의들), 본체: 본체값 }
}

function 정의찾기(정의표: Map<string, 칩정의>, 이름: string) -> 칩정의 {
  정의표.getOr(이름, 빈정의)
}

function 놓인칩으로(정의표: Map<string, 칩정의>, 배치: 인스턴스) -> 놓인칩 {
  { 배치: 배치, 정의: 정의찾기(정의표, 배치.칩이름) }
}

// 같은 아이디의 인스턴스가 여럿이면 마지막 것을 씁니다.
function 칩찾기(정의표: Map<string, 칩정의>, 인스턴스들: Array<인스턴스>, 아이디: string) -> Option<놓인칩> {
  let 맞는배치들 = 인스턴스들.filter(배치 => 배치.아이디 == 아이디)
  if 맞는배치들.length == 0 { None } else { Some(놓인칩으로(정의표, 맞는배치들[맞는배치들.length - 1])) }
}

export function 놓인칩찾기(분해값: 분해결과, 아이디: string) -> Option<놓인칩> {
  칩찾기(분해값.정의표, 분해값.본체.인스턴스들, 아이디)
}

export function 놓인칩들(분해값: 분해결과) -> Array<놓인칩> {
  분해값.본체.인스턴스들.map(배치 => 놓인칩으로(분해값.정의표, 배치))
}

// 한 내부 노드에 외부 핀이 여럿 붙으면 마지막 핀을 씁니다.
function 외부입력핀(정의: 칩정의, 노드아이디: string) -> string {
  정의.외부입력표.getOr(노드아이디, "")
}

function 출력터미널인가(정의: 칩정의, 노드아이디: string) -> bool {
  노드아이디 in 정의.출력터미널들
}

// 같은 노드에 외부 출력 핀이 여럿 붙어도 존재만 묻습니다. 칩 정의를 만들 때 한 번 모읍니다.
function 출력터미널모으기(핀들: Array<외부핀>) -> Set<string> {
  let mut 노드들: Set<string> = Set.of()
  for 핀값 in 핀들 { 노드들.add(핀값.노드) }
  노드들
}

function 터미널인가(정의: 칩정의, 노드아이디: string) -> bool {
  외부입력핀(정의, 노드아이디) != "" || 출력터미널인가(정의, 노드아이디)
}

function 구동자(본체값: 회로본체, 노드: string, 핀: string) -> 끝점 {
  본체값.구동표.getOr(노드 + ":" + 핀, 빈끝점)
}

function 시작바꾸기(선: netlist.전선, 원천: 끝점) -> netlist.전선 {
  선{ 시작노드: 원천.노드, 시작핀: 원천.핀 }
}

// 칩 출력 핀을 칩 안에서 그 핀을 구동하는 원시 노드로 바꿉니다. 칩 정의는 이미 평탄하고 인스턴스 아이디에는 __ 가 없어 한 번만 내려가면 됩니다.
export function 평탄원천(분해값: 분해결과, 노드: string, 핀: string) -> 끝점 {
  match 놓인칩찾기(분해값, 노드) {
    case None => ({ 노드: 노드, 핀: 핀 })
    case Some(칩) => {
      let 터미널 = 칩.정의.외부출력표.getOr(핀, "")
      let 원천 = 터미널구동자(칩.정의, 터미널)
      if 터미널 == "" || 원천.노드 == "" { 빈끝점 } else { { 노드: 이름공간(노드, 원천.노드), 핀: 원천.핀 } }
    }
  }
}

function 터미널구동자(정의: 칩정의, 노드: string) -> 끝점 {
  정의.터미널구동표.getOr(노드, 빈끝점)
}

// 칩 입력 핀의 신호는 바깥에서 그 핀을 구동하는 원시 노드가 들고 있습니다.
export function 칩입력원천(분해값: 분해결과, 칩아이디: string, 핀: string) -> 끝점 {
  let 구동 = 구동자(분해값.본체, 칩아이디, 핀)
  평탄원천(분해값, 구동.노드, 구동.핀)
}

// extin 터미널에서 나가는 칩 안 전선은 바깥 구동자에 바로 잇고, extout 터미널로 가는 전선은 바깥쪽 전선이 대신합니다.
function 칩안전선(분해값: 분해결과, 칩: 놓인칩, 선: netlist.전선) -> netlist.전선 {
  let 입력핀 = 외부입력핀(칩.정의, 선.시작노드)
  let 원천 = if 입력핀 == "" { { 노드: 이름공간(칩.배치.아이디, 선.시작노드), 핀: 선.시작핀 } } else { 칩입력원천(분해값, 칩.배치.아이디, 입력핀) }
  { 시작노드: 원천.노드, 시작핀: 원천.핀, 끝노드: 이름공간(칩.배치.아이디, 선.끝노드), 끝핀: 선.끝핀 }
}

// 본체 노드, 칩마다 터미널을 뺀 내부 노드, 원시 사이 전선, 칩 안 전선, 칩 출력에서 나가는 전선 순서입니다.
// 이 순서가 신호 벡터와 정착 상태 문자열의 순서이므로 바꾸지 않습니다.
export function 평탄화(분해값: 분해결과) -> netlist.회로 {
  let 본체값 = 분해값.본체
  let 칩아이디들 = 본체값.인스턴스들.map(배치 => 배치.아이디)
  let 칩들 = 놓인칩들(분해값)
  let 칩노드들 = [for 칩 in 칩들 for 노드값 in 칩.정의.내부.노드들 if !터미널인가(칩.정의, 노드값.아이디) => 노드값{
    아이디: 이름공간(칩.배치.아이디, 노드값.아이디),
    x: 칩.배치.x + 노드값.x,
    y: 칩.배치.y + 노드값.y,
  }]
  let 원시전선들 = 본체값.전선들.filter(선 => !(선.시작노드 in 칩아이디들) && !(선.끝노드 in 칩아이디들))
  let 칩안전선들 = [for 칩 in 칩들 for 선 in 칩.정의.내부.전선들 if !출력터미널인가(칩.정의, 선.끝노드) => 칩안전선(분해값, 칩, 선)]
  let 칩출력전선들 = [for 선 in 본체값.전선들 if 선.시작노드 in 칩아이디들 && !(선.끝노드 in 칩아이디들) => 시작바꾸기(선, 평탄원천(분해값, 선.시작노드, 선.시작핀))]
  {
    노드들: [for 묶음 in [본체값.노드들, 칩노드들] for 노드값 in 묶음 => 노드값],
    전선들: [for 묶음 in [원시전선들, 칩안전선들, 칩출력전선들] for 선 in 묶음 if 선.시작노드 != "" => 선],
  }
}

function 줄머리로(토큰: string) -> 줄머리 {
  match 토큰 {
    case "chipdef" => "chipdef"
    case "endchip" => "endchip"
    case "node" => "node"
    case "wire" => "wire"
    case "chip" => "chip"
    case "extin" => "extin"
    case "extout" => "extout"
    case _ => "기타"
  }
}

function 알수없는줄(칩안: bool, 머리: string) -> string {
  if 칩안 { "칩 정의 안의 알 수 없는 줄입니다: " + 머리 } else { "알 수 없는 줄입니다: " + 머리 }
}

function 칩머리읽기(토큰들: Array<string>) -> Result<string, string> {
  if 토큰들.length != 2 { return Err("chipdef 줄 형식은 chipdef 이름 입니다") }
  if !util.안전아이디인가(토큰들[1]) { return Err("유효하지 않은 칩 이름입니다: " + 토큰들[1]) }
  Ok(토큰들[1])
}

function 인스턴스줄파싱(토큰들: Array<string>) -> Result<인스턴스, string> {
  if 토큰들.length != 5 { return Err("chip 줄 형식은 chip 인스턴스 칩이름 x y 입니다") }
  if !util.안전아이디인가(토큰들[1]) { return Err("유효하지 않은 인스턴스 아이디입니다: " + 토큰들[1]) }
  if !util.안전아이디인가(토큰들[2]) { return Err("유효하지 않은 칩 이름입니다: " + 토큰들[2]) }
  if !util.정수인가(토큰들[3]) || !util.정수인가(토큰들[4]) { return Err("칩 좌표는 정수여야 합니다") }
  Ok({ 아이디: 토큰들[1], 칩이름: 토큰들[2], x: toInt(토큰들[3]) ?? 0, y: toInt(토큰들[4]) ?? 0 })
}

// 토큰이 셋이 아닌 extin 과 extout 은 알 수 없는 줄로 봅니다.
function 외부핀줄읽기(칩안: bool, 머리: 줄머리, 토큰들: Array<string>) -> Result<본체항목, string> {
  if !칩안 || 토큰들.length != 3 { return Err(알수없는줄(칩안, 토큰들[0])) }
  if !util.안전아이디인가(토큰들[1]) { return Err("유효하지 않은 외부 핀 이름입니다: " + 토큰들[1]) }
  let 핀값 = { 핀: 토큰들[1], 노드: 토큰들[2] }
  Ok(if 머리 == "extin" { 본체항목.외부입력줄(핀값) } else { 본체항목.외부출력줄(핀값) })
}

function 본체줄읽기(칩안: bool, 머리: 줄머리, 토큰들: Array<string>) -> Result<본체항목, string> {
  match 머리 {
    case "node" => Ok(본체항목.노드줄(토큰들))
    case "wire" => Ok(본체항목.전선줄(netlist.전선줄파싱(토큰들)?))
    case "chip" => Ok(본체항목.칩줄(인스턴스줄파싱(토큰들)?))
    case "extin" | "extout" => 외부핀줄읽기(칩안, 머리, 토큰들)
    case "chipdef" | "endchip" | "기타" => Err(알수없는줄(칩안, 토큰들[0]))
  }
}

function 본체모으기(항목들: Array<본체항목>) -> 생본체 {
  let mut 노드줄들: Array<Array<string>> = []
  let mut 전선들: Array<netlist.전선> = []
  let mut 인스턴스들: Array<인스턴스> = []
  let mut 외부입력: Array<외부핀> = []
  let mut 외부출력: Array<외부핀> = []
  for 항목 in 항목들 {
    match 항목 {
      case 노드줄(토큰들) => { 노드줄들.push(토큰들) }
      case 전선줄(선) => { 전선들.push(선) }
      case 칩줄(배치) => { 인스턴스들.push(배치) }
      case 외부입력줄(핀값) => { 외부입력.push(핀값) }
      case 외부출력줄(핀값) => { 외부출력.push(핀값) }
    }
  }
  { 노드줄들: 노드줄들, 전선들: 전선들, 인스턴스들: 인스턴스들, 외부입력: 외부입력, 외부출력: 외부출력 }
}

function 칩핀없음인가(정의표: Map<string, 칩정의>, 인스턴스들: Array<인스턴스>, 노드: string, 핀: string, 쪽: 핀쪽) -> bool {
  match 칩찾기(정의표, 인스턴스들, 노드) {
    case None => false
    case Some(칩) => {
      let 핀들 = match 쪽 {
        case "입력" => 칩.정의.외부입력
        case "출력" => 칩.정의.외부출력
      }
      핀들.filter(핀값 => 핀값.핀 == 핀).length == 0
    }
  }
}

// 원시 노드끼리의 전선은 netlist 가 검증하고, 칩에 닿는 전선은 그 칩이 선언한 extin 이나 extout 핀이어야 합니다.
function 본체검증(정의들: Array<칩정의>, 생본체값: 생본체) -> Result<회로본체, string> {
  let 칩아이디들 = 생본체값.인스턴스들.map(배치 => 배치.아이디)
  let 원시전선들 = 생본체값.전선들.filter(선 => !(선.시작노드 in 칩아이디들) && !(선.끝노드 in 칩아이디들))
  let 원시회로 = netlist.회로만들기(생본체값.노드줄들, 원시전선들)?
  let 없는칩들 = 생본체값.인스턴스들.filter(배치 => 정의들.filter(정의 => 정의.이름 == 배치.칩이름).length == 0)
  if 없는칩들.length > 0 { return Err("정의되지 않은 칩입니다: " + 없는칩들[0].칩이름) }
  let 정의표 = 정의표만들기(정의들)
  for 선 in 생본체값.전선들 {
    if 칩핀없음인가(정의표, 생본체값.인스턴스들, 선.시작노드, 선.시작핀, "출력") { return Err("없는 외부 핀입니다: " + 선.시작노드 + ":" + 선.시작핀) }
    if 칩핀없음인가(정의표, 생본체값.인스턴스들, 선.끝노드, 선.끝핀, "입력") { return Err("없는 외부 핀입니다: " + 선.끝노드 + ":" + 선.끝핀) }
  }
  Ok({ 노드들: 원시회로.노드들, 전선들: 생본체값.전선들, 인스턴스들: 생본체값.인스턴스들, 구동표: 구동표만들기(생본체값.전선들) })
}

function 정의만들기(앞정의들: Array<칩정의>, 생정의: 생칩정의) -> Result<칩정의, string> {
  let 본체값 = match 본체검증(앞정의들, 생정의.본체) {
    case Ok(값) => 값
    case Err(오류) => return Err("칩 " + 생정의.이름 + ": " + 오류)
  }
  let 아이디들 = 본체값.노드들.map(노드값 => 노드값.아이디)
  let 빠진입력들 = 생정의.본체.외부입력.filter(핀값 => !(핀값.노드 in 아이디들))
  if 빠진입력들.length > 0 { return Err("칩 " + 생정의.이름 + "의 extin " + 빠진입력들[0].핀 + "이 없는 내부 노드를 가리킵니다: " + 빠진입력들[0].노드) }
  let 빠진출력들 = 생정의.본체.외부출력.filter(핀값 => !(핀값.노드 in 아이디들))
  if 빠진출력들.length > 0 { return Err("칩 " + 생정의.이름 + "의 extout " + 빠진출력들[0].핀 + "이 없는 내부 노드를 가리킵니다: " + 빠진출력들[0].노드) }
  if 앞정의들.filter(정의 => 정의.이름 == 생정의.이름).length > 0 { return Err("칩 정의가 중복됩니다: " + 생정의.이름) }
  let 내부 = 평탄화(분해결과만들기(앞정의들, 본체값))
  Ok({
    이름: 생정의.이름,
    본체: 본체값,
    내부: 내부,
    외부입력: 생정의.본체.외부입력,
    외부출력: 생정의.본체.외부출력,
    외부입력표: 노드에서핀표(생정의.본체.외부입력),
    외부출력표: 핀에서노드표(생정의.본체.외부출력),
    출력터미널들: 출력터미널모으기(생정의.본체.외부출력),
    터미널구동표: 터미널구동표만들기(내부.전선들),
  })
}

// 인스턴스 이름공간이 부모 노드와 부딪히는 경우를 펼친 뒤에 잡습니다.
function 충돌검사(분해값: 분해결과) -> Result<분해결과, string> {
  let mut 본아이디들: Set<string> = Set.of()
  for 노드값 in 평탄화(분해값).노드들 {
    if 노드값.아이디 in 본아이디들 { return Err("노드 아이디가 충돌합니다: " + 노드값.아이디) }
    본아이디들.add(노드값.아이디)
  }
  Ok(분해값)
}

// 줄 오류를 먼저 모두 보고, 그다음 칩 정의를 정의 순서로, 마지막으로 최상위 본체를 검증합니다. 첫 오류에서 멈춥니다.
export function 분해(원문: string) -> Result<분해결과, string> {
  let mut 생정의들: Array<생칩정의> = []
  let mut 최상위항목들: Array<본체항목> = []
  let mut 칩항목들: Array<본체항목> = []
  let mut 칩이름 = ""
  let mut 칩안 = false
  for 토큰들 in 원문.split("\n").map(util.토큰나누기).filter(줄토큰들 => 줄토큰들.length > 0) {
    let 머리 = 줄머리로(토큰들[0])
    if 칩안 && 머리 == "endchip" {
      생정의들.push({ 이름: 칩이름, 본체: 본체모으기(칩항목들) })
      칩안 = false
    } else if !칩안 && 머리 == "chipdef" {
      칩이름 = 칩머리읽기(토큰들)?
      칩항목들 = []
      칩안 = true
    } else if 칩안 {
      칩항목들.push(본체줄읽기(true, 머리, 토큰들)?)
    } else {
      최상위항목들.push(본체줄읽기(false, 머리, 토큰들)?)
    }
  }
  if 칩안 { return Err("endchip가 없습니다") }
  let mut 정의들: Array<칩정의> = []
  for 생정의 in 생정의들 { 정의들.push(정의만들기(정의들, 생정의)?) }
  let 본체값 = 본체검증(정의들, 본체모으기(최상위항목들))?
  충돌검사(분해결과만들기(정의들, 본체값))
}

function 전역신호(시뮬평탄: sim.시뮬결과, 접두: string, 노드값: netlist.노드) -> sim.노드신호 {
  if netlist.입력노드인가(노드값.종류) {
    { 아이디: 노드값.아이디, 신호: 노드값.값 == 1, 프레임: 0 }
  } else {
    let 전역 = sim.신호로(시뮬평탄, 접두 + 노드값.아이디)
    { 아이디: 노드값.아이디, 신호: 전역.신호, 프레임: 전역.프레임 }
  }
}

// 다시 평가하면 순환에 막히는 단계는 전역 정착 벡터를 투영해 칩 안 래치의 상태를 그대로 보여 줍니다.
// 전역에 없는 노드는 이 단계의 extout 터미널이라 구동 전선의 시작 노드 신호로 채웁니다. 앞에서 채운 값을 뒤에서 읽으므로 차례대로 채웁니다.
// 채우는 동안 아이디는 바뀌지 않으므로 자리표와 구동표는 한 번만 만듭니다.
function 정착투영(시뮬평탄: sim.시뮬결과, 접두: string, 평탄단계: netlist.회로) -> sim.시뮬결과 {
  let mut 신호들 = 평탄단계.노드들.map(노드값 => 전역신호(시뮬평탄, 접두, 노드값))
  let 자리표 = sim.자리표만들기(신호들)
  let 구동표 = 터미널구동표만들기(평탄단계.전선들)
  for i in 0..<신호들.length {
    if 신호들[i].프레임 < 0 {
      let 원천자리 = 자리표.getOr(구동표.getOr(신호들[i].아이디, 빈끝점).노드, -1)
      신호들[i] = 신호들[i]{ 신호: 원천자리 >= 0 && 신호들[원천자리].신호, 프레임: 0 }
    }
  }
  sim.시뮬만들기(신호들, 1)
}

function 경계값넣기(위: 보기단계, 칩: 놓인칩, 노드값: netlist.노드) -> netlist.노드 {
  let 핀 = 외부입력핀(칩.정의, 노드값.아이디)
  if 핀 == "" { return 노드값 }
  let 켜짐 = sim.신호로(위.시뮬, 칩입력원천(위.분해, 칩.배치.아이디, 핀).노드).신호
  노드값{ 값: if 켜짐 { 1 } else { 0 } }
}

// 칩 안의 extin 터미널을 바깥 경계 신호로 채워 그 단계만 따로 평가합니다. 고장은 단계마다 접두를 한 겹씩 벗깁니다.
function 한단계내려가기(위: 보기단계, 칩: 놓인칩, 시뮬평탄: sim.시뮬결과, 접두: string) -> 보기단계 {
  let 칩본체 = 칩.정의.본체
  let 분해값 = 위.분해{ 본체: 칩본체{ 노드들: 칩본체.노드들.map(노드값 => 경계값넣기(위, 칩, 노드값)) } }
  let 고장들 = fault.내부고장(위.고장들, 칩.배치.아이디)
  let 평탄단계 = fault.고장회로(평탄화(분해값), 고장들)
  let 조합 = sim.평가회로(평탄단계)
  { 분해: 분해값, 시뮬: if sim.되먹임인가(조합) { 정착투영(시뮬평탄, 접두, 평탄단계) } else { 조합 }, 고장들: 고장들 }
}

// 경로의 인스턴스마다 한 단계씩 내려갑니다. 빈 경로는 최상위 그대로입니다.
export function 경로뷰(분해값: 분해결과, 시뮬평탄: sim.시뮬결과, 경로: Array<string>, 고장들: Array<fault.고장>) -> Result<경로보기, string> {
  let mut 단계값: 보기단계 = { 분해: 분해값, 시뮬: 시뮬평탄, 고장들: 고장들 }
  let mut 이름들: Array<string> = []
  for i in 0..<경로.length {
    let 칩 = match 놓인칩찾기(단계값.분해, 경로[i]) {
      case Some(찾은칩) => 찾은칩
      case None => return Err("뷰가 없는 인스턴스를 가리킵니다: " + 경로[i])
    }
    이름들.push(칩.배치.칩이름)
    단계값 = 한단계내려가기(단계값, 칩, 시뮬평탄, 경로.slice(0, i + 1).map(아이디 => 아이디 + "__").join(""))
  }
  Ok({ 단계: 단계값, 이름들: 이름들 })
}
