-
Notifications
You must be signed in to change notification settings - Fork 0
/
file_context.go
139 lines (123 loc) · 3.67 KB
/
file_context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package db
import (
"errors"
"fmt"
"math/big"
"github.com/alecthomas/participle/v2/lexer"
)
// An expr represents the possible values we can get after fully parsing a
// subexpression. These values can be:
// - a constant with [exprConst]
// - a variable with [exprVar]
// - a difference of two variables with [exprDiff]
// - a boolean literal with [exprLit]
type expr interface {
expr()
}
//go-sumtype:decl expr
type exprConst struct {
Val *big.Int
}
type exprVar struct {
Var VariableID
}
type exprDiff struct {
X VariableID
Y VariableID
}
type exprLit struct {
Lit Lit
}
// The convertExpr function tries to convert an [expr] to one of its subtypes,
// specified by T. It returns an error if the conversion failed.
func convertExpr[T expr](e expr) (ret T, err error) {
// Try to do the conversion
ret, ok := e.(T)
// Populate err if it failed
if !ok {
// Compute the error message based on the type. We have to use a hack to
// figure out what the target type is.
//
// See: https://appliedgo.com/blog/a-tip-and-a-trick-when-working-with-generics
var test T
switch any(test).(type) {
case exprConst:
err = errors.New("expected constant")
case exprVar:
err = errors.New("expected variable")
case exprDiff:
err = errors.New("expected difference of two symbols")
case exprLit:
err = errors.New("expected Bool")
}
}
return
}
// The convertExprAt function is a wrapper around [convertExpr], which adds
// location information to the error on failure.
func convertExprAt[T expr](e expr, pos lexer.Position) (ret T, err error) {
// Do the normal convertion.
ret, err = convertExpr[T](e)
// If it failed, append location information.
if err != nil {
err = fmt.Errorf("%s at :%d:%d", err.Error(), pos.Line, pos.Column)
}
return
}
func (e exprConst) expr() {}
func (e exprVar) expr() {}
func (e exprDiff) expr() {}
func (e exprLit) expr() {}
// A context stores all the names in scope at any given time, and what [expr]
// they map to. It also has a parent context, which is nil for the root.
type context struct {
Names map[string]expr
Parent *context
}
// The AddName method associates a name with a value in the current context.
// It throws an error if the name has already been defined at the current level.
// It's fine if the name has been defined on previous levels - that's shadowing.
func (ctx *context) AddName(name string, value expr) error {
// Check if the name is already in
_, ok := ctx.Names[name]
if ok {
return fmt.Errorf("duplicate name '%s'", name)
}
// Otherwise, add it to the map
ctx.Names[name] = value
return nil
}
// The Lookup method looks up a name in the given context. It returns the
// value of that name, or an error if the name could not be found.
func (ctx context) Lookup(name string) (expr, error) {
// Check to see if we know it.
val, ok := ctx.Names[name]
if ok {
return val, nil
}
// Otherwise, check our parent.
if ctx.Parent != nil {
return ctx.Parent.Lookup(name)
}
// If we don't have a parent, that's an error.
return exprLit{}, fmt.Errorf("unknown symbol '%s'", name)
}
// The LookupAt function is a wrapper around [Lookup] that adds position
// information to the error. If no error occurred, it is identical to [Lookup].
func (ctx context) LookupAt(name string, pos lexer.Position) (ret expr, err error) {
// Do the normal lookup.
ret, err = ctx.Lookup(name)
// If it failed, append location information.
if err != nil {
err = fmt.Errorf("%s at :%d:%d", err.Error(), pos.Line, pos.Column)
}
// Done
return
}
// The MakeChild method creates a child of the current context with no names.
func (ctx context) MakeChild() context {
return context{
Names: make(map[string]expr),
Parent: &ctx,
}
}