- commit
- bb5eec9a32f7437967a32e1e548f7d67041495b9
- parent
- fe81beba7385737045d8436dd5dd4e6fdb1b4ef5
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 359 | ++++++++++++++++++------------ |
@@ -212,150 +212,227 @@ func lotusRef(col, row uint16, at sheet.Addr, v wk1Version) (string, bool) {
212212 213213 // lotusFormula decodes the bytecode of the formula in cell at.214214 func lotusFormula(code []byte, at sheet.Addr, v wk1Version) (string, bool) {215- var stack []expr216- ok := true217- pop := func() (expr, bool) {218- if len(stack) == 0 {219- return expr{}, false215+ d := &lotusDecoder{code: code, at: at, version: v, ok: true}216+ for d.i < len(code) {217+ if !d.step() {218+ return "", false220219 }221- e := stack[len(stack)-1]222- stack = stack[:len(stack)-1]223- return e, true224220 }225- push := func(text string, power int) { stack = append(stack, expr{text, power}) }226- u16 := func(i int) uint16 { return binary.LittleEndian.Uint16(code[i:]) }227-228- for i := 0; i < len(code); {229- op := code[i]230- switch {231- case op == 0x00: // number232- if i+9 > len(code) {233- return "", false234- }235- f := math.Float64frombits(binary.LittleEndian.Uint64(code[i+1:]))236- if math.IsNaN(f) || math.IsInf(f, 0) {237- return "", false238- }239- if f < 0 {240- push(numInput(f), unaryPower)241- } else {242- push(numInput(f), atomPower)243- }244- i += 9245- case op == 0x01: // cell246- if i+5 > len(code) {247- return "", false248- }249- ref, good := lotusRef(u16(i+1), u16(i+3), at, v)250- ok = ok && good251- push(ref, atomPower)252- i += 5253- case op == 0x02: // range254- if i+9 > len(code) {255- return "", false256- }257- from, good1 := lotusRef(u16(i+1), u16(i+3), at, v)258- to, good2 := lotusRef(u16(i+5), u16(i+7), at, v)259- ok = ok && good1 && good2260- push(from+":"+to, atomPower)261- i += 9262- case op == 0x03: // end263- i = len(code)264- case op == 0x04: // parentheses, as written265- e, good := pop()266- if !good {267- return "", false268- }269- push("("+e.text+")", atomPower)270- i++271- case op == 0x05: // integer272- if i+3 > len(code) {273- return "", false274- }275- n := int16(u16(i + 1))276- if n < 0 {277- push(strconv.Itoa(int(n)), unaryPower)278- } else {279- push(strconv.Itoa(int(n)), atomPower)280- }281- i += 3282- case op == 0x06: // string283- end := indexByte(code[i+1:], 0)284- if end < 0 {285- return "", false286- }287- s := lics(string(code[i+1 : i+1+end]))288- push(`"`+strings.ReplaceAll(s, `"`, `""`)+`"`, atomPower)289- i += end + 2290- case op == 0x08 || op == 0x17 || op == 0x16: // - + #NOT#291- x, good := pop()292- if !good {293- return "", false294- }295- sign, power := "-", unaryPower296- switch op {297- case 0x17:298- sign = "+"299- case 0x16:300- sign, power = "#NOT#", notPower301- }302- if x.power < power {303- x.text = "(" + x.text + ")"304- }305- push(sign+x.text, power)306- i++307- case lotusBinary[op].op != "":308- bin := lotusBinary[op]309- r, good1 := pop()310- l, good2 := pop()311- if !good1 || !good2 {312- return "", false313- }314- if l.power < bin.power {315- l.text = "(" + l.text + ")"316- }317- if r.power <= bin.power {318- r.text = "(" + r.text + ")"319- }320- push(l.text+bin.op+r.text, bin.power)321- i++322- default:323- fn, known := lotusFuncs[op]324- if !known {325- return "", false326- }327- n, size := fn.args, 1328- if n < 0 {329- if i+1 >= len(code) {330- return "", false331- }332- n, size = int(code[i+1]), 2333- }334- if n > len(stack) {335- return "", false336- }337- args := make([]string, n)338- for k := range args {339- args[k] = stack[len(stack)-n+k].text340- }341- stack = stack[:len(stack)-n]342- text := ""343- if fn.write != nil && (fn.args >= 0 || n > 0) {344- text = fn.write(args)345- }346- if text == "" {347- ok = false348- text = "@" + fn.name349- if n > 0 {350- text += "(" + strings.Join(args, ",") + ")"351- }352- }353- push(text, atomPower)354- i += size221+ if len(d.stack) != 1 {222+ return "", false223+ }224+ return d.stack[0].text, d.ok225+}226+227+// lotusDecoder runs a formula's bytecode on a stack of subexpressions.228+// ok turns false when a part has no equivalent in 012; decoding goes on229+// so the formula can still be written in 1-2-3's spelling.230+type lotusDecoder struct {231+ code []byte232+ i int // the next opcode233+ at sheet.Addr234+ version wk1Version235+ stack []expr236+ ok bool237+}238+239+// lotusOperands decode the opcodes that are neither binary operators nor240+// functions. Each reports false when the bytecode is malformed.241+var lotusOperands = map[byte]func(*lotusDecoder) bool{242+ 0x00: (*lotusDecoder).number,243+ 0x01: (*lotusDecoder).cell,244+ 0x02: (*lotusDecoder).cellRange,245+ 0x03: (*lotusDecoder).end,246+ 0x04: (*lotusDecoder).parens,247+ 0x05: (*lotusDecoder).integer,248+ 0x06: (*lotusDecoder).str,249+ 0x08: unary("-", unaryPower),250+ 0x16: unary("#NOT#", notPower),251+ 0x17: unary("+", unaryPower),252+}253+254+// step decodes the opcode at d.i, reporting false when the bytecode is255+// malformed.256+func (d *lotusDecoder) step() bool {257+ op := d.code[d.i]258+ if decode, ok := lotusOperands[op]; ok {259+ return decode(d)260+ }261+ if bin, ok := lotusBinary[op]; ok {262+ return d.binary(bin.op, bin.power)263+ }264+ if fn, ok := lotusFuncs[op]; ok {265+ return d.function(fn)266+ }267+ return false268+}269+270+func (d *lotusDecoder) push(text string, power int) {271+ d.stack = append(d.stack, expr{text, power})272+}273+274+func (d *lotusDecoder) pop() (expr, bool) {275+ if len(d.stack) == 0 {276+ return expr{}, false277+ }278+ e := d.stack[len(d.stack)-1]279+ d.stack = d.stack[:len(d.stack)-1]280+ return e, true281+}282+283+// has reports whether n bytes of operands follow the opcode.284+func (d *lotusDecoder) has(n int) bool { return d.i+1+n <= len(d.code) }285+286+func (d *lotusDecoder) u16(at int) uint16 { return binary.LittleEndian.Uint16(d.code[at:]) }287+288+// literal pushes a number; a negative one binds as a unary minus does.289+func (d *lotusDecoder) literal(text string, negative bool) {290+ if negative {291+ d.push(text, unaryPower)292+ } else {293+ d.push(text, atomPower)294+ }295+}296+297+func (d *lotusDecoder) number() bool {298+ if !d.has(8) {299+ return false300+ }301+ f := math.Float64frombits(binary.LittleEndian.Uint64(d.code[d.i+1:]))302+ if math.IsNaN(f) || math.IsInf(f, 0) {303+ return false304+ }305+ d.literal(numInput(f), f < 0)306+ d.i += 9307+ return true308+}309+310+func (d *lotusDecoder) integer() bool {311+ if !d.has(2) {312+ return false313+ }314+ n := int16(d.u16(d.i + 1))315+ d.literal(strconv.Itoa(int(n)), n < 0)316+ d.i += 3317+ return true318+}319+320+// ref decodes the cell reference whose coordinates start at code[at].321+func (d *lotusDecoder) ref(at int) string {322+ ref, good := lotusRef(d.u16(at), d.u16(at+2), d.at, d.version)323+ d.ok = d.ok && good324+ return ref325+}326+327+func (d *lotusDecoder) cell() bool {328+ if !d.has(4) {329+ return false330+ }331+ d.push(d.ref(d.i+1), atomPower)332+ d.i += 5333+ return true334+}335+336+func (d *lotusDecoder) cellRange() bool {337+ if !d.has(8) {338+ return false339+ }340+ from, to := d.ref(d.i+1), d.ref(d.i+5)341+ d.push(from+":"+to, atomPower)342+ d.i += 9343+ return true344+}345+346+func (d *lotusDecoder) end() bool {347+ d.i = len(d.code)348+ return true349+}350+351+// parens are parentheses as the formula was written.352+func (d *lotusDecoder) parens() bool {353+ e, good := d.pop()354+ if !good {355+ return false356+ }357+ d.push("("+e.text+")", atomPower)358+ d.i++359+ return true360+}361+362+func (d *lotusDecoder) str() bool {363+ end := indexByte(d.code[d.i+1:], 0)364+ if end < 0 {365+ return false366+ }367+ s := lics(string(d.code[d.i+1 : d.i+1+end]))368+ d.push(`"`+strings.ReplaceAll(s, `"`, `""`)+`"`, atomPower)369+ d.i += end + 2370+ return true371+}372+373+// unary decodes a prefix operator that binds with the given power.374+func unary(sign string, power int) func(*lotusDecoder) bool {375+ return func(d *lotusDecoder) bool {376+ x, good := d.pop()377+ if !good {378+ return false355379 }380+ if x.power < power {381+ x.text = "(" + x.text + ")"382+ }383+ d.push(sign+x.text, power)384+ d.i++385+ return true356386 }357- if len(stack) != 1 {358- return "", false387+}388+389+func (d *lotusDecoder) binary(op string, power int) bool {390+ r, good1 := d.pop()391+ l, good2 := d.pop()392+ if !good1 || !good2 {393+ return false394+ }395+ if l.power < power {396+ l.text = "(" + l.text + ")"397+ }398+ if r.power <= power {399+ r.text = "(" + r.text + ")"400+ }401+ d.push(l.text+op+r.text, power)402+ d.i++403+ return true404+}405+406+// function decodes a call whose arguments are on the stack. A function407+// with no equivalent is written as 1-2-3 spells it, clearing ok.408+func (d *lotusDecoder) function(fn lotusFunc) bool {409+ n, size := fn.args, 1410+ if n < 0 {411+ if !d.has(1) {412+ return false413+ }414+ n, size = int(d.code[d.i+1]), 2415+ }416+ if n > len(d.stack) {417+ return false418+ }419+ args := make([]string, n)420+ for k := range args {421+ args[k] = d.stack[len(d.stack)-n+k].text422+ }423+ d.stack = d.stack[:len(d.stack)-n]424+ text := ""425+ if fn.write != nil && (fn.args >= 0 || n > 0) {426+ text = fn.write(args)427+ }428+ if text == "" {429+ d.ok = false430+ text = "@" + fn.name431+ if n > 0 {432+ text += "(" + strings.Join(args, ",") + ")"433+ }359434 }360- return stack[0].text, ok435+ d.push(text, atomPower)436+ d.i += size437+ return true361438 }