// Tacky.
//
// See README.

package main

import (
  "fmt"
  "os"
  "strconv"
  "strings"
)

type Expr struct {
  op string
  s string
  kid []*Expr
}

func (e *Expr) AddKid(kid *Expr) {
  e.kid = append(e.kid, kid)
}

func NewExpr(op, s string) *Expr {
  return &Expr{op, s, nil}
}

func NewOp(op string, a *Expr, b *Expr) *Expr {
  return &Expr{op, "", []*Expr{a, b}}
}

func NewFun(op string, args *Expr) *Expr {
  s := strings.TrimRight(op, "(")
  e := NewExpr(s, s)
  e.kid = args.kid
  return e
}

type formLine struct {
  id string
  desc string
  expr *Expr
  cached bool
  n int64
}

type form struct {
  id string
  lines map[string]*formLine
  sortedIds []string
  wholeDollarsOnly bool
}

type Tacky struct {
  forms map[string]*form
  curForm *form
  curLine string
  curFlag string
  sortedIds []string
  wholeDollarsOnly bool
}

func (tax *Tacky) Expr(e *Expr) {
  if tax.curLine == "" {
    if tax.curFlag == "" {
      panic("assignment expected")
    }
    if e.op != "ID" {
      panic("flag must be 0 or 1")
    }
    switch e.s {
    case "1":
      tax.wholeDollarsOnly = true
    case "0":
      tax.wholeDollarsOnly = false
    default:
      panic("flag must be 0 or 1")
    }
    tax.curFlag = ""
    return
  }
  form := tax.curForm
  line, ok := form.lines[tax.curLine]
  if !ok {
    panic("BUG: missing line " + tax.curLine)
  }
  if line.expr == nil {
    line.expr = e
    return
  }
  line.expr = NewOp("+", line.expr, e)
}

func (tax *Tacky) Assign(s string) {
  form := tax.curForm
  s = strings.TrimRight(s, "=")
  v := strings.SplitN(s, " ", 2)
  id := v[0]
  if form == nil {
    if id != "whole_dollars_only" {
      panic("no such flag: " + id)
    }
    tax.curFlag = id
    return
  }
  desc := ""
  if len(v) == 2 {
    desc = v[1]
  }
  if _, ok := form.lines[id]; ok {
    panic("duplicate line definition: " + id)
  }
  form.lines[id] = &formLine{id, strings.TrimSpace(desc), nil, false, 0}
  form.sortedIds = append(form.sortedIds, id)
  tax.curLine = id
}

func (tax *Tacky) BeginForm(line string) {
  if tax.curForm != nil {
    panic("nested form")
  }
  id := strings.TrimSpace(strings.TrimRight(line, "{"))
  if _, ok := tax.forms[id]; ok {
    panic("duplicate form: " + id)
  }
  tax.curForm = &form{id, make(map[string]*formLine), nil, tax.wholeDollarsOnly}
  tax.curLine = ""
  tax.sortedIds = append(tax.sortedIds, id)
  tax.forms[id] = tax.curForm
}

func (tax *Tacky) EndForm() {
  tax.curForm = nil
  tax.curLine = ""
}

func atoi(s string) int64 {
  n, e := strconv.Atoi(s)
  if e != nil {
    panic(e)
  }
  return int64(n)
}

func clip(a int64) int64 {
  if a < 0 {
    return 0
  }
  return a
}

func min(a, b int64) int64 {
  if a < b {
    return a
  }
  return b
}

func max(a, b int64) int64 {
  if a > b {
    return a
  }
  return b
}

func (tax *Tacky) Eval(e *Expr) int64 {
  if e == nil {  // Undefined.
    return 0
  }
  switch e.op {
  case "$":
    s := strings.TrimLeft(e.s, "$")
    v := strings.Split(s, ".")
    if len(v) == 2 {
      return atoi(v[0]) * 100 + atoi(v[1])
    }
    if len(v) != 1 {
      panic("bad money: " + e.s)
    }
    return atoi(s) * 100
  case "ID":
    if e.s == "0" {
      return 0
    }
    return tax.Get(e.s)
  case "+":
    return tax.Eval(e.kid[0]) + tax.Eval(e.kid[1])
  case "-":
    return tax.Eval(e.kid[0]) - tax.Eval(e.kid[1])
  case "*":
    if e.kid[1].op == "%" {
      v := strings.Split(strings.TrimRight(e.kid[1].s, "%"), ".")
      p := atoi(v[0])
      q := int64(100)
      if len(v) == 2 {
        for _ = range v[1] {
          p *= 10
          q *= 10
        }
        p += atoi(v[1])
      } else if len(v) != 1 {
        panic("malformed percentage")
      }
      return (tax.Eval(e.kid[0]) * p + q / 2) / q
    }
    return tax.Eval(e.kid[0]) * tax.Eval(e.kid[1])
  case "/":
    return tax.Eval(e.kid[0]) / tax.Eval(e.kid[1])
  case "XREF":
    v := strings.Split(e.s, ":")
    return tax.XGet(strings.TrimLeft(v[0], "["), strings.TrimRight(v[1], "]"))
  case "clip":
    if len(e.kid) != 1 {
      panic("bad arg count")
    }
    return clip(tax.Eval(e.kid[0]))
  case "min":
    if len(e.kid) != 2 {
      panic("bad arg count")
    }
    return min(tax.Eval(e.kid[0]), tax.Eval(e.kid[1]))
  case "max":
    if len(e.kid) != 2 {
      panic("bad arg count")
    }
    return max(tax.Eval(e.kid[0]), tax.Eval(e.kid[1]))
  }
  panic("no such op: " + e.op)
}

func (tax *Tacky) Get(id string) int64 {
  line, ok := tax.curForm.lines[id]
  if !ok {
    panic("no such line: " + id)
  }
  if line.cached {
    return line.n
  }
  line.cached = true
  line.n = tax.Eval(line.expr)
  if tax.curForm.wholeDollarsOnly {
    if (line.n >= 0) {
      line.n = (line.n + 50) / 100 * 100
    } else {
      line.n = (line.n - 50) / 100 * 100
    }
  }
  return line.n
}

func (tax *Tacky) XGet(id, line string) int64 {
  form, ok := tax.forms[id]
  if !ok {
    panic("no such form: " + id)
  }
  orig := tax.curForm
  tax.curForm = form
  n := tax.Get(line)
  tax.curForm = orig
  return n
}

func dollarize(cents int64) string {
  if cents < 0 {
    return "(" + dollarize(-cents) + ")"
  }
  return fmt.Sprintf("$%d.%02d", cents/100, cents%100)
}

func main() {
  tax := new(Tacky)
  tax.forms = make(map[string]*form)
  yyParse(NewLexerWithInit(os.Stdin, func(y *Lexer) { y.p = tax }))

  for _, id := range tax.sortedIds {
    f := tax.forms[id]
    tax.curForm = f
    fmt.Println(f.id)
    for _, id := range f.sortedIds {
      fmt.Println(id, f.lines[id].desc, dollarize(tax.Get(id)))
    }
    fmt.Println()
  }
}
