oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package calc
2
3import (
4 "fmt"
5 "strings"
6)
7
8// Parse compiles a formula's source text (without a leading "=") into an
9// AST. Errors report only a byte position and a reason; the formula's own
10// content never appears in an error string.
11//
12// Precedence, loosest to tightest: comparison (= <> < <= > >=), then
13// concatenation (&), then + -, then * /, then ^ (right-associative), then
14// unary minus, then primary. Unary minus binding tighter than ^ matches
15// spreadsheet convention (Excel: -2^2 = 4, not -4), which is the reverse
16// of most programming languages and is easy to get wrong later, so it is
17// pinned by TestParseUnaryBindsTighterThanPower.
18func Parse(src string) (Node, error) {
19 toks, err := lex(src)
20 if err != nil {
21 return nil, err
22 }
23 p := &parser{toks: toks}
24 p.cur = toks[0]
25
26 n, err := p.parseComparison()
27 if err != nil {
28 return nil, err
29 }
30 if p.cur.kind != tEOF {
31 return nil, p.errorf("unexpected trailing input")
32 }
33 return n, nil
34}
35
36type parser struct {
37 toks []token
38 pos int
39 cur token
40}
41
42func (p *parser) advance() {
43 p.pos++
44 if p.pos < len(p.toks) {
45 p.cur = p.toks[p.pos]
46 } else {
47 p.cur = token{kind: tEOF, pos: p.cur.pos}
48 }
49}
50
51func (p *parser) errorf(reason string) error {
52 return fmt.Errorf("calc: parse error at position %d: %s", p.cur.pos, reason)
53}
54
55var comparisonOps = map[string]bool{"=": true, "<>": true, "<": true, "<=": true, ">": true, ">=": true}
56var concatOps = map[string]bool{"&": true}
57var additiveOps = map[string]bool{"+": true, "-": true}
58var termOps = map[string]bool{"*": true, "/": true}
59
60// parseBinaryLevel implements one left-associative precedence level shared
61// by comparison, concatenation, addition and multiplication: parse one
62// operand via next, then fold in any run of same-level operators.
63func (p *parser) parseBinaryLevel(ops map[string]bool, next func() (Node, error)) (Node, error) {
64 left, err := next()
65 if err != nil {
66 return nil, err
67 }
68 for p.cur.kind == tOp && ops[p.cur.op] {
69 op := p.cur.op
70 p.advance()
71 right, err := next()
72 if err != nil {
73 return nil, err
74 }
75 left = &Binary{Op: op, Left: left, Right: right}
76 }
77 return left, nil
78}
79
80func (p *parser) parseComparison() (Node, error) {
81 return p.parseBinaryLevel(comparisonOps, p.parseConcat)
82}
83
84func (p *parser) parseConcat() (Node, error) {
85 return p.parseBinaryLevel(concatOps, p.parseAdditive)
86}
87
88func (p *parser) parseAdditive() (Node, error) {
89 return p.parseBinaryLevel(additiveOps, p.parseTerm)
90}
91
92func (p *parser) parseTerm() (Node, error) {
93 return p.parseBinaryLevel(termOps, p.parsePower)
94}
95
96// parsePower is right-associative: 2^3^2 = 2^(3^2).
97func (p *parser) parsePower() (Node, error) {
98 left, err := p.parseUnary()
99 if err != nil {
100 return nil, err
101 }
102 if p.cur.kind == tOp && p.cur.op == "^" {
103 p.advance()
104 right, err := p.parsePower()
105 if err != nil {
106 return nil, err
107 }
108 return &Binary{Op: "^", Left: left, Right: right}, nil
109 }
110 return left, nil
111}
112
113// parseUnary binds tighter than parsePower: see the precedence note on Parse.
114func (p *parser) parseUnary() (Node, error) {
115 if p.cur.kind == tOp && p.cur.op == "-" {
116 p.advance()
117 x, err := p.parseUnary()
118 if err != nil {
119 return nil, err
120 }
121 return &Unary{Op: "-", X: x}, nil
122 }
123 return p.parsePrimary()
124}
125
126func (p *parser) parsePrimary() (Node, error) {
127 switch p.cur.kind {
128 case tNum:
129 v := p.cur.num
130 p.advance()
131 return &Num{V: v}, nil
132 case tStr:
133 v := p.cur.str
134 p.advance()
135 return &Str{V: v}, nil
136 case tRef:
137 return p.parseRefOrRange()
138 case tLParen:
139 p.advance()
140 inner, err := p.parseComparison()
141 if err != nil {
142 return nil, err
143 }
144 if p.cur.kind != tRParen {
145 return nil, p.errorf("expected closing parenthesis")
146 }
147 p.advance()
148 return inner, nil
149 case tIdent:
150 return p.parseIdentOrCall()
151 default:
152 return nil, p.errorf("unexpected token, want expression")
153 }
154}
155
156func (p *parser) parseRefOrRange() (Node, error) {
157 from := refNode(p.cur.ref)
158 p.advance()
159 if p.cur.kind != tColon {
160 return from, nil
161 }
162 p.advance()
163 if p.cur.kind != tRef {
164 return nil, p.errorf("expected reference after ':'")
165 }
166 to := refNode(p.cur.ref)
167 p.advance()
168 return &Range{From: from, To: to}, nil
169}
170
171func refNode(r refToken) *Ref {
172 return &Ref{ColPos: r.colPos, RowPos: r.rowPos, AbsCol: r.absCol, AbsRow: r.absRow}
173}
174
175func (p *parser) parseIdentOrCall() (Node, error) {
176 name := p.cur.ident
177 p.advance()
178
179 switch strings.ToUpper(name) {
180 case "TRUE":
181 return &Bool{V: true}, nil
182 case "FALSE":
183 return &Bool{V: false}, nil
184 }
185
186 if p.cur.kind != tLParen {
187 return nil, p.errorf("unexpected identifier, want function call")
188 }
189 args, err := p.parseCallArgs()
190 if err != nil {
191 return nil, err
192 }
193 return &Call{Name: strings.ToUpper(name), Args: args}, nil
194}
195