orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package tools
2
3import (
4 "fmt"
5 "go/ast"
6 "go/parser"
7 "go/token"
8 "strconv"
9)
10
11// evalExpr walks a parsed Go expression rather than shelling out to anything.
12// go/parser is already in the standard library and it rejects everything that
13// is not an expression for free, so the whole risk surface is the node types
14// allowed below.
15func evalExpr(s string) (float64, error) {
16 node, err := parser.ParseExpr(s)
17 if err != nil {
18 return 0, fmt.Errorf("that is not an arithmetic expression")
19 }
20 return evalNode(node)
21}
22
23func evalNode(n ast.Expr) (float64, error) {
24 switch e := n.(type) {
25 case *ast.BasicLit:
26 switch e.Kind {
27 case token.INT, token.FLOAT:
28 return strconv.ParseFloat(e.Value, 64)
29 }
30 return 0, fmt.Errorf("only numbers are allowed")
31 case *ast.ParenExpr:
32 return evalNode(e.X)
33 case *ast.UnaryExpr:
34 v, err := evalNode(e.X)
35 if err != nil {
36 return 0, err
37 }
38 switch e.Op {
39 case token.SUB:
40 return -v, nil
41 case token.ADD:
42 return v, nil
43 }
44 return 0, fmt.Errorf("unsupported sign")
45 case *ast.BinaryExpr:
46 l, err := evalNode(e.X)
47 if err != nil {
48 return 0, err
49 }
50 r, err := evalNode(e.Y)
51 if err != nil {
52 return 0, err
53 }
54 switch e.Op {
55 case token.ADD:
56 return l + r, nil
57 case token.SUB:
58 return l - r, nil
59 case token.MUL:
60 return l * r, nil
61 case token.QUO:
62 if r == 0 {
63 return 0, fmt.Errorf("division by zero")
64 }
65 return l / r, nil
66 case token.REM:
67 if int64(r) == 0 {
68 return 0, fmt.Errorf("division by zero")
69 }
70 return float64(int64(l) % int64(r)), nil
71 }
72 return 0, fmt.Errorf("unsupported operator %s", e.Op)
73 }
74 return 0, fmt.Errorf("only arithmetic is supported")
75}