-
Notifications
You must be signed in to change notification settings - Fork 0
/
semantic.go
154 lines (135 loc) · 3.09 KB
/
semantic.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
// WACC Group 34
//
// semantic.go: TODO
//
// TODO
import (
"sync"
)
// mergeErrors merges all the errors in the channel
func mergeErrors(cs []<-chan error) <-chan error {
var wg sync.WaitGroup
out := make(chan error)
// Start an output goroutine for each input channel in cs. output
// copies values from c to out until c is closed, then calls wg.Done
output := func(c <-chan error) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// Start a goroutine to close out once all the output goroutines are
// done. This must start after the wg.Add call
go func() {
wg.Wait()
close(out)
}()
return out
}
// hasReturn returns true if the statement is/has a return value
func hasReturn(stm Statement) bool {
if stm == nil {
return false
}
switch t := stm.(type) {
case *BlockStatement:
return hasReturn(t.body) || hasReturn(t.next)
case *ReturnStatement:
return true
case *ExitStatement:
return true
case *IfStatement:
return (hasReturn(t.trueStat) && hasReturn(t.falseStat)) ||
hasReturn(t.next)
default:
return hasReturn(t.GetNext())
}
}
// checkFunctionReturns checks if the function has a return type. If it is
// missing it creates an error
func checkFunctionReturns(f *FunctionDef) <-chan error {
out := make(chan error)
go func() {
switch f.returnType.(type) {
case VoidType:
default:
returns := hasReturn(f.body)
if !returns {
out <- CreateMissingReturnError(f.token, f.ident)
}
}
close(out)
}()
return out
}
// checkJunkStatement checks if there are statments after the function returns
// or exits and produces an error accordingly
func checkJunkStatement(stm Statement) <-chan error {
out := make(chan error)
go func() {
switch t := stm.(type) {
case *BlockStatement:
for err := range checkJunkStatement(t.body) {
out <- err
}
case *IfStatement:
for err := range checkJunkStatement(t.trueStat) {
out <- err
}
for err := range checkJunkStatement(t.falseStat) {
out <- err
}
case *WhileStatement:
for err := range checkJunkStatement(t.body) {
out <- err
}
case *ForStatement:
for err := range checkJunkStatement(t.init) {
out <- err
}
for err := range checkJunkStatement(t.after) {
out <- err
}
for err := range checkJunkStatement(t.body) {
out <- err
}
case *ReturnStatement:
if n := t.next; n != nil {
out <- CreateUnreachableStatementError(
stm.Token(),
)
}
}
if n := stm.GetNext(); n != nil {
for err := range checkJunkStatement(n) {
out <- err
}
}
close(out)
}()
return out
}
// CheckFunctionCodePaths checks for a return or exit statment in the function
// It also removes the deadcode from the function
func (m *AST) CheckFunctionCodePaths() (errs []error) {
var errorChannels []<-chan error
for _, f := range m.functions {
errorChannels = append(
errorChannels,
checkFunctionReturns(f),
)
errorChannels = append(
errorChannels,
checkJunkStatement(f.body),
)
}
for err := range mergeErrors(errorChannels) {
errs = append(errs, err)
}
return
}