tacky.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Tacky.
  2. //
  3. // See README.
  4. package main
  5. import (
  6. "fmt"
  7. "os"
  8. "strconv"
  9. "strings"
  10. )
  11. type Expr struct {
  12. op string
  13. s string
  14. kid []*Expr
  15. }
  16. func (e *Expr) AddKid(kid *Expr) {
  17. e.kid = append(e.kid, kid)
  18. }
  19. func NewExpr(op, s string) *Expr {
  20. return &Expr{op, s, nil}
  21. }
  22. func NewOp(op string, a *Expr, b *Expr) *Expr {
  23. return &Expr{op, "", []*Expr{a, b}}
  24. }
  25. func NewFun(op string, args *Expr) *Expr {
  26. s := strings.TrimRight(op, "(")
  27. e := NewExpr(s, s)
  28. e.kid = args.kid
  29. return e
  30. }
  31. type formLine struct {
  32. id string
  33. desc string
  34. expr *Expr
  35. cached bool
  36. n int64
  37. }
  38. type form struct {
  39. id string
  40. lines map[string]*formLine
  41. sortedIds []string
  42. wholeDollarsOnly bool
  43. }
  44. type Tacky struct {
  45. forms map[string]*form
  46. curForm *form
  47. curLine string
  48. curFlag string
  49. sortedIds []string
  50. wholeDollarsOnly bool
  51. }
  52. func (tax *Tacky) Expr(e *Expr) {
  53. if tax.curLine == "" {
  54. if tax.curFlag == "" {
  55. panic("assignment expected")
  56. }
  57. if e.op != "ID" {
  58. panic("flag must be 0 or 1")
  59. }
  60. switch e.s {
  61. case "1":
  62. tax.wholeDollarsOnly = true
  63. case "0":
  64. tax.wholeDollarsOnly = false
  65. default:
  66. panic("flag must be 0 or 1")
  67. }
  68. tax.curFlag = ""
  69. return
  70. }
  71. form := tax.curForm
  72. line, ok := form.lines[tax.curLine]
  73. if !ok {
  74. panic("BUG: missing line " + tax.curLine)
  75. }
  76. if line.expr == nil {
  77. line.expr = e
  78. return
  79. }
  80. line.expr = NewOp("+", line.expr, e)
  81. }
  82. func (tax *Tacky) Assign(s string) {
  83. form := tax.curForm
  84. s = strings.TrimRight(s, "=")
  85. v := strings.SplitN(s, " ", 2)
  86. id := v[0]
  87. if form == nil {
  88. if id != "whole_dollars_only" {
  89. panic("no such flag: " + id)
  90. }
  91. tax.curFlag = id
  92. return
  93. }
  94. desc := ""
  95. if len(v) == 2 {
  96. desc = v[1]
  97. }
  98. if _, ok := form.lines[id]; ok {
  99. panic("duplicate line definition: " + id)
  100. }
  101. form.lines[id] = &formLine{id, strings.TrimSpace(desc), nil, false, 0}
  102. form.sortedIds = append(form.sortedIds, id)
  103. tax.curLine = id
  104. }
  105. func (tax *Tacky) BeginForm(line string) {
  106. if tax.curForm != nil {
  107. panic("nested form")
  108. }
  109. id := strings.TrimSpace(strings.TrimRight(line, "{"))
  110. if _, ok := tax.forms[id]; ok {
  111. panic("duplicate form: " + id)
  112. }
  113. tax.curForm = &form{id, make(map[string]*formLine), nil, tax.wholeDollarsOnly}
  114. tax.curLine = ""
  115. tax.sortedIds = append(tax.sortedIds, id)
  116. tax.forms[id] = tax.curForm
  117. }
  118. func (tax *Tacky) EndForm() {
  119. tax.curForm = nil
  120. tax.curLine = ""
  121. }
  122. func atoi(s string) int64 {
  123. n, e := strconv.Atoi(s)
  124. if e != nil {
  125. panic(e)
  126. }
  127. return int64(n)
  128. }
  129. func clip(a int64) int64 {
  130. if a < 0 {
  131. return 0
  132. }
  133. return a
  134. }
  135. func min(a, b int64) int64 {
  136. if a < b {
  137. return a
  138. }
  139. return b
  140. }
  141. func max(a, b int64) int64 {
  142. if a > b {
  143. return a
  144. }
  145. return b
  146. }
  147. func (tax *Tacky) Eval(e *Expr) int64 {
  148. if e == nil { // Undefined.
  149. return 0
  150. }
  151. switch e.op {
  152. case "$":
  153. s := strings.TrimLeft(e.s, "$")
  154. v := strings.Split(s, ".")
  155. if len(v) == 2 {
  156. return atoi(v[0]) * 100 + atoi(v[1])
  157. }
  158. if len(v) != 1 {
  159. panic("bad money: " + e.s)
  160. }
  161. return atoi(s) * 100
  162. case "ID":
  163. if e.s == "0" {
  164. return 0
  165. }
  166. return tax.Get(e.s)
  167. case "+":
  168. return tax.Eval(e.kid[0]) + tax.Eval(e.kid[1])
  169. case "-":
  170. return tax.Eval(e.kid[0]) - tax.Eval(e.kid[1])
  171. case "*":
  172. if e.kid[1].op == "%" {
  173. v := strings.Split(strings.TrimRight(e.kid[1].s, "%"), ".")
  174. p := atoi(v[0])
  175. q := int64(100)
  176. if len(v) == 2 {
  177. for _ = range v[1] {
  178. p *= 10
  179. q *= 10
  180. }
  181. p += atoi(v[1])
  182. } else if len(v) != 1 {
  183. panic("malformed percentage")
  184. }
  185. return (tax.Eval(e.kid[0]) * p + q / 2) / q
  186. }
  187. return tax.Eval(e.kid[0]) * tax.Eval(e.kid[1])
  188. case "/":
  189. return tax.Eval(e.kid[0]) / tax.Eval(e.kid[1])
  190. case "XREF":
  191. v := strings.Split(e.s, ":")
  192. return tax.XGet(strings.TrimLeft(v[0], "["), strings.TrimRight(v[1], "]"))
  193. case "clip":
  194. if len(e.kid) != 1 {
  195. panic("bad arg count")
  196. }
  197. return clip(tax.Eval(e.kid[0]))
  198. case "min":
  199. if len(e.kid) != 2 {
  200. panic("bad arg count")
  201. }
  202. return min(tax.Eval(e.kid[0]), tax.Eval(e.kid[1]))
  203. case "max":
  204. if len(e.kid) != 2 {
  205. panic("bad arg count")
  206. }
  207. return max(tax.Eval(e.kid[0]), tax.Eval(e.kid[1]))
  208. }
  209. panic("no such op: " + e.op)
  210. }
  211. func (tax *Tacky) Get(id string) int64 {
  212. line, ok := tax.curForm.lines[id]
  213. if !ok {
  214. panic("no such line: " + id)
  215. }
  216. if line.cached {
  217. return line.n
  218. }
  219. line.cached = true
  220. line.n = tax.Eval(line.expr)
  221. if tax.curForm.wholeDollarsOnly {
  222. if (line.n >= 0) {
  223. line.n = (line.n + 50) / 100 * 100
  224. } else {
  225. line.n = (line.n - 50) / 100 * 100
  226. }
  227. }
  228. return line.n
  229. }
  230. func (tax *Tacky) XGet(id, line string) int64 {
  231. form, ok := tax.forms[id]
  232. if !ok {
  233. panic("no such form: " + id)
  234. }
  235. orig := tax.curForm
  236. tax.curForm = form
  237. n := tax.Get(line)
  238. tax.curForm = orig
  239. return n
  240. }
  241. func dollarize(cents int64) string {
  242. if cents < 0 {
  243. return "(" + dollarize(-cents) + ")"
  244. }
  245. return fmt.Sprintf("$%d.%02d", cents/100, cents%100)
  246. }
  247. func main() {
  248. tax := new(Tacky)
  249. tax.forms = make(map[string]*form)
  250. yyParse(NewLexerWithInit(os.Stdin, func(y *Lexer) { y.p = tax }))
  251. for _, id := range tax.sortedIds {
  252. f := tax.forms[id]
  253. tax.curForm = f
  254. fmt.Println(f.id)
  255. for _, id := range f.sortedIds {
  256. fmt.Println(id, f.lines[id].desc, dollarize(tax.Get(id)))
  257. }
  258. fmt.Println()
  259. }
  260. }