tally/cell.go

454 lines
8.3 KiB
Go

package main
import (
"fmt"
"math"
"strconv"
"strings"
)
const (
Bold int = 1
Faint int = 2
Italic int = 3
Underline int = 4
)
type cell struct {
kind int // Enum for the type of cell
rawVal string // A raw string of the value
num float64 // A quick reference field for the current value
expr []string // Fields representing an expression
mask string // What the user sees
mods []int // Enums of modifiers on the cell
}
func (c cell) String(width int, selected bool) string {
mods := c.Mods()
if selected {
mods += "\033[7m"
}
format := "%s %*.*s \033[0m"
if c.kind == Text {
format = "%s %-*.*s \033[0m"
}
return fmt.Sprintf(format, mods, width-2, width-2, c.mask)
}
func (c cell) Mods() string {
var o strings.Builder
for _, mod := range c.mods {
o.WriteString(fmt.Sprintf("\033[%dm", mod))
}
return o.String()
}
func (c *cell) ToggleMod(mod int) {
if mod > 4 || mod < 1 {
return
}
present := false
for i, m := range c.mods {
if m == mod {
if i != len(c.mods)-1 {
c.mods[i] = c.mods[len(c.mods)-1]
}
c.mods = c.mods[:len(c.mods)-1]
present = true
break
}
}
if !present {
c.mods = append(c.mods, mod)
}
}
func (c *cell) Edit(row, col, width int, text bool) {
line := GetEditLine(fmt.Sprintf("%c%d > ", col+64, row), c.rawVal, width)
if len(line) == 0 {
return
}
fmt.Print("\033[?25l")
line = strings.TrimSpace(line)
if text && (!strings.HasPrefix(line, "\"") && !strings.HasSuffix(line, "\"")){
line = fmt.Sprintf("\"%s\"", line)
}
updated := c.Update(line)
if updated {
modified = true
}
}
func (c *cell) Update(val string) bool {
if val == c.rawVal || len(val) == 0 {
// If nothing was changed or the change is empty just return
return false
}
c.rawVal = val
num, err := strconv.ParseFloat(val, 64)
if err == nil {
c.kind = Number
c.num = num
c.mask = strconv.FormatFloat(num, 'f', -1, 64)
} else if strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"") {
c.rawVal = strings.Replace(c.rawVal, " ", "\\_", -1)
c.kind = Text
c.mask = strings.Replace(val[1:len(val)-1], "\\_", " ", -1)
} else {
c.kind = Expr
c.expr = strings.Fields(val)
}
return true
}
func (c *cell) Calculate() {
if c.kind != Expr {
return
}
insideString := false
s := makeStack()
for _, v := range c.expr {
var stackError error = nil
if IsAddr(v) {
pt, _, _ := Addr2Point(v)
c, err := GetCell(pt)
if err != nil {
c.mask = "#Err: Invalid cell reference " + v
return
}
if c.kind == Text {
v = c.rawVal
} else if c.kind == Empty {
break
} else {
v = c.mask
}
} else if IsRange(v) {
stackError = s.Sum(v)
goto ERRCHECK
}
switch true {
case strings.HasPrefix(v, "\""):
if !strings.HasSuffix(v, "\"") {
insideString = true
}
s.str.WriteString(strings.Replace(v, "\"", "", -1))
s.text = true
case insideString:
if strings.HasSuffix(v, "\"") {
insideString = false
v = strings.Replace(v, "\"", "", -1)
}
s.str.WriteRune(' ')
s.str.WriteString(v)
case IsFunc(v):
v = strings.ToUpper(v)
if v == "SPC" {
s.str.WriteRune(' ')
s.text = true
}
if v == "+" {
stackError = s.Add()
}
if v == "-" {
stackError = s.Subtract()
}
if v == "/" {
stackError = s.Divide()
}
if v == "*" {
stackError = s.Multiply()
}
if v == "." {
if s.Len() < 1 {
stackError = fmt.Errorf("Stack underflow")
}
num := s.Pop()
s.str.WriteString(strconv.FormatFloat(num, 'f', -1, 64))
s.text = true
}
if v == "MIN" {
stackError = s.Min()
}
if v == "MAX" {
stackError = s.Max()
}
if v == "ROUND" {
stackError = s.Round()
}
if v == "SWAP" {
stackError = s.Swap()
}
if v == "DUP" {
stackError = s.Dup()
}
if v == "CLEAR" {
s.ClearStack()
}
if v == "DROP" {
stackError = s.Drop()
}
if v == "OVER" {
stackError = s.Over()
}
if v == "POW" {
stackError = s.Pow()
}
if v == "FLOOR" {
stackError = s.Floor()
}
if v == "CEIL" {
stackError = s.Ceil()
}
default:
v, err := strconv.ParseFloat(v, 64)
if err != nil {
c.mask = "#Err: Cannot parse float"
return
}
s.Push(v)
}
ERRCHECK: if stackError != nil {
c.mask = fmt.Sprintf("#ERR: %s", stackError.Error())
return
}
}
if s.text {
if s.ptr != -1 {
for s.Len() > 0 {
num := s.Pop()
s.str.WriteString(strconv.FormatFloat(num, 'f', -1, 64))
}
}
c.mask = strings.Replace(s.str.String(), "\\_", " ", -1)
} else if s.ptr == 0 {
c.mask = strconv.FormatFloat(s.data[0], 'f', -1, 64)
} else if s.ptr == -1 {
c.mask = ""
} else {
c.mask = "#Err: Invalid Formula"
}
}
type stack struct {
data [100]float64
ptr int
text bool
str strings.Builder
}
func makeStack() stack {
return stack{data: [100]float64{}, ptr: -1, text: false}
}
func (s *stack) Push(v float64) {
if s.ptr >= 99 {
panic("Stack overflow")
}
s.ptr++
s.data[s.ptr] = v
}
func (s *stack) Pop() float64 {
if s.ptr < 0 {
panic("Stack underflow")
}
s.ptr--
return s.data[s.ptr+1]
}
func (s stack) Len() int {
return s.ptr + 1
}
func (s *stack) Dup() error {
if s.Len() < 1 {
return fmt.Errorf("Stack underflow")
}
val := s.Pop()
s.Push(val)
s.Push(val)
return nil
}
func (s *stack) Drop() error {
if s.Len() < 1 {
return fmt.Errorf("Stack underflow")
}
s.Pop()
return nil
}
func (s *stack) ClearStack() {
s.ptr = -1
}
func (s *stack) Add() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
if s.text {
val := strconv.FormatFloat(s.Pop(), 'f', -1, 64)
s.str.WriteString(val)
} else {
s.Push(s.Pop() + s.Pop())
}
return nil
}
func (s *stack) Subtract() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
second := s.Pop()
s.Push(s.Pop() - second)
return nil
}
func (s *stack) Multiply() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
s.Push(s.Pop() * s.Pop())
return nil
}
func (s *stack) Divide() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
second := s.Pop()
s.Push(s.Pop() / second)
return nil
}
func (s *stack) Min() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
one := s.Pop()
two := s.Pop()
if one < two {
s.Push(one)
} else {
s.Push(two)
}
return nil
}
func (s *stack) Max() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
one := s.Pop()
two := s.Pop()
if one > two {
s.Push(one)
} else {
s.Push(two)
}
return nil
}
func (s *stack) Round() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
decimals := s.Pop()
num := s.Pop()
s.Push(math.Round(num*math.Pow(10, decimals)) / math.Pow(10, decimals))
return nil
}
func (s *stack) Over() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
tos := s.Pop()
cpy := s.Pop()
s.Push(cpy)
s.Push(tos)
s.Push(cpy)
return nil
}
func (s *stack) Swap() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
tos := s.Pop()
newTos := s.Pop()
s.Push(tos)
s.Push(newTos)
return nil
}
func (s *stack) Pow() error {
if s.Len() < 2 {
return fmt.Errorf("Stack underflow")
}
exp := s.Pop()
base := s.Pop()
s.Push(math.Pow(base, exp))
return nil
}
func (s *stack) Floor() error {
if s.Len() < 1 {
return fmt.Errorf("Stack underflow")
}
s.Push(math.Floor(s.Pop()))
return nil
}
func (s *stack) Ceil() error {
if s.Len() < 1 {
return fmt.Errorf("Stack underflow")
}
s.Push(math.Ceil(s.Pop()))
return nil
}
func (s *stack) Sum(rng string) error {
points := strings.Split(rng[:len(rng)-1], ":")
pointA, _, _ := Addr2Point(points[0])
pointB, _, _ := Addr2Point(points[1])
rowStart := pointA.row
rowEnd := pointB.row
colStart := pointA.col
colEnd := pointB.col
if rowEnd < rowStart || colEnd < colStart {
return fmt.Errorf("Range starts after range end")
}
if colEnd > wb.sheets[wb.sheet].cols-1 || rowEnd > wb.sheets[wb.sheet].rows-1 || colStart < 0 || rowStart < 0 {
return fmt.Errorf("Range out of bounds")
}
var sum float64 = 0.0
var val float64
var c cell
var err error
cells := wb.sheets[wb.sheet].cells
for row := rowStart; row <= rowEnd; row++ {
for col := colStart; col <= colEnd; col++ {
c = cells[row][col]
val = c.num
if c.kind == Expr {
val, err = strconv.ParseFloat(c.mask, 64)
if err != nil {
continue
}
}
sum += val
}
}
s.Push(sum)
return nil
}