/
include.go
170 lines (151 loc) · 3.61 KB
/
include.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"path"
"strings"
"time"
)
var callCount int
// IncludeFile sends out one file, expanding embdded includes as needed.
func IncludeFile(name string, level int) bool {
f, err := os.Open(name)
if err != nil {
fmt.Println(err)
return false
}
defer f.Close()
currDir := path.Dir(name)
currFile := path.Base(name)
currLine := 0
if level == 0 {
callCount = 0
}
callCount++
prefix := fmt.Sprintf("%d>", callCount)
lastMsg := ""
defer func() {
statusMsg(lastMsg, "")
}()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
currLine++
lastMsg = statusMsg(lastMsg, "%s %s %d: ", prefix, currFile, currLine)
line := scanner.Text()
s := strings.TrimLeft(line, " ")
if s == "" || s == "\\" || strings.HasPrefix(s, "\\ ") {
continue // don't send empty or comment-only lines
}
if strings.HasPrefix(line, "include ") {
for _, fname := range strings.Fields(line)[1:] {
statusMsg(lastMsg, "")
if !IncludeFile(path.Join(currDir, fname), level+1) {
return false
}
}
} else {
serialSend <- []byte(line + "\r")
if !match(line) {
return false
}
}
}
return true
}
// statusMsg prints a formatted string and returns it. It takes the previous
// string to be able to clear it before outputting the new message.
func statusMsg(prev string, desc string, args ...interface{}) string {
msg := fmt.Sprintf(desc, args...)
n := len(msg)
// FIXME this optimisation is incorrect, it sometimes eats up first 3 chars
if false && n > 3 && n == len(prev) && msg[:n-3] == prev[:n-3] {
fmt.Print("\b\b\b", msg[n-3:]) // optimise if only end changes
} else {
if len(msg) < len(prev) {
fmt.Print("\r", strings.Repeat(" ", len(prev)))
}
fmt.Print("\r", msg)
}
return msg
}
func match(expect string) bool {
timer := time.NewTimer(3 * time.Second)
var pending []byte
for {
select {
case data := <-serialRecv:
pending = append(pending, data...)
if !timer.Stop() {
<-timer.C
}
timer.Reset(time.Second)
case <-commandSend:
return false // abort include
case <-time.After(10 * time.Millisecond):
if !bytes.Contains(pending, []byte{'\n'}) {
continue
}
lines := bytes.Split(pending, []byte{'\n'})
n := len(lines)
for i := 0; i < n-2; i++ {
fmt.Printf("%s\n", lines[i])
}
lines = lines[n-2:]
last := string(lines[0])
if len(lines[1]) == 0 {
hasExpected := strings.HasPrefix(last, expect+" ")
if hasExpected || strings.HasSuffix(last, " ok.") {
if last != expect+" ok." {
msg := last
// only show output if source does not start with "("
// ... in that case, show just the comment up to ")"
if hasExpected {
msg = last[len(expect)+1:]
if last[0] == '(' {
if n := strings.Index(expect, ")"); n > 0 {
msg = last[:n+1] + last[len(expect):]
}
}
}
if msg == "" {
return true // don't show empty [if]-skipped lines
}
fmt.Printf("%s\n", msg)
if hasFatalError(last) {
return false // no point in keeping going
}
}
return true
}
}
fmt.Printf("%s\n", last)
pending = lines[1]
case <-timer.C:
if len(pending) == 0 {
return true
}
fmt.Printf("%s (timeout)\n", pending)
return string(pending) == expect+" "
}
}
}
func hasFatalError(s string) bool {
for _, match := range []string{
" not found.",
" is compile-only.",
" Stack not balanced.",
" Stack underflow",
" Stack overflow",
" Flash full",
" Ram full",
" Structures don't match",
" Jump too far",
} {
if strings.HasSuffix(s, match) {
return true
}
}
return false
}