-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
233 lines (203 loc) · 4.65 KB
/
main.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//
// Trivial "compiler" for BrainFuck
//
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
)
// Given an input brainfuck program generate a comparable assembly version.
//
// Return the filename we generated, or error.
func generateProgram(source string) (string, error) {
var buff bytes.Buffer
var programStart = `
global _start
section .text
_start:
mov r8, stack
`
buff.WriteString(programStart)
//
// Keep track of "[" here.
//
// These are loop opens.
//
opens := []int{}
i := 0
ln := len(source)
for i < ln {
switch source[i] {
case '>':
end := i
for source[end] == '>' {
end++
}
buff.WriteString(fmt.Sprintf(" add r8, %d\n", end-i))
i = end - 1
case '<':
end := i
for source[end] == '<' {
end++
}
buff.WriteString(fmt.Sprintf(" sub r8, %d\n", end-i))
i = end - 1
case '+':
end := i
for source[end] == '+' {
end++
}
buff.WriteString(fmt.Sprintf(" add byte [r8], %d\n", end-i))
i = end - 1
case '-':
end := i
for source[end] == '-' {
end++
}
buff.WriteString(fmt.Sprintf(" sub byte [r8], %d\n", end-i))
i = end - 1
case '.':
// output
buff.WriteString(" mov rax, 1\n") // SYS_WRITE
buff.WriteString(" mov rdi, 1\n") // STDOUT
buff.WriteString(" mov rsi, r8\n") // data-comes-here
buff.WriteString(" mov rdx, 1\n") // one byte
buff.WriteString(" syscall\n") // Syscall
case ',':
// input
buff.WriteString(" mov rax, 0\n") // SYS_READ
buff.WriteString(" mov rdi, 0\n") // STDIN
buff.WriteString(" mov rsi, r8\n") // Dest
buff.WriteString(" mov rdx, 1\n") // one byte
buff.WriteString(" syscall\n") // syscall
case '[':
//
// Open of a block.
//
// If the index-value is zero then jump to the
// end of the while-loop.
//
buff.WriteString(fmt.Sprintf("label_loop_%d:\n", i))
buff.WriteString(" cmp byte [r8], 0\n")
buff.WriteString(fmt.Sprintf(" je close_loop_%d\n", i))
opens = append(opens, i)
case ']':
// "]" can only follow an "[".
//
// Every time we see a "[" we save the ID onto a
// temporary stack. So we're gonna go back to the
// most recent open.
//
// This will cope with nesting.
//
last := opens[len(opens)-1]
opens = opens[:len(opens)-1]
buff.WriteString(fmt.Sprintf(" jmp label_loop_%d\n", last))
buff.WriteString(fmt.Sprintf("close_loop_%d:\n", last))
}
i++
}
// terminate
buff.WriteString(" mov rax, 60\n")
buff.WriteString(" mov rdi, 0\n")
buff.WriteString(" syscall\n")
// program-area
buff.WriteString("section .bss\n")
buff.WriteString("stack: resb 300000\n")
tmpfile, err := ioutil.TempFile("", "bfcc*.s")
if err != nil {
return "", err
}
if _, err := tmpfile.Write(buff.Bytes()); err != nil {
return "", err
}
if err := tmpfile.Close(); err != nil {
return "", err
}
return tmpfile.Name(), nil
}
func main() {
//
// Parse command-line flags
//
compile := flag.Bool("compile", true, "Compile the assembly file, after generation.")
cleanup := flag.Bool("cleanup", true, "Remove the temporary assembly file after creation.")
run := flag.Bool("run", false, "Run the program after compiling.")
flag.Parse()
//
// Get the input filename
//
if len(flag.Args()) < 1 {
fmt.Printf("Usage: bfcc [flags] input.file.bf [outfile]\n")
return
}
//
// Input and output files
//
input := flag.Args()[0]
output := "a.out"
if len(flag.Args()) == 2 {
output = flag.Args()[1]
}
//
// Read the input program.
//
prog, err := ioutil.ReadFile(input)
if err != nil {
fmt.Printf("failed to read input file %s: %s\n", input, err.Error())
return
}
//
// "Compile"
//
path, err := generateProgram(string(prog))
if err != nil {
fmt.Printf("error writing output: %s\n", err.Error())
return
}
//
// Compile
//
if *compile {
// nasm to compile to object-code
nasm := exec.Command("nasm", "-f", "elf64", "-o", fmt.Sprintf("%s.o", output), path)
nasm.Stdout = os.Stdout
nasm.Stderr = os.Stderr
err = nasm.Run()
if err != nil {
fmt.Printf("Error launching nasm: %s\n", err)
return
}
// ld to link to an executable
ld := exec.Command("ld", "-m", "elf_x86_64", "-o", output, fmt.Sprintf("%s.o", output))
ld.Stdout = os.Stdout
ld.Stderr = os.Stderr
err = ld.Run()
if err != nil {
fmt.Printf("Error launching ld: %s\n", err)
return
}
}
if *run {
exe := exec.Command(output)
exe.Stdout = os.Stdout
exe.Stderr = os.Stderr
err = exe.Run()
if err != nil {
fmt.Printf("Error launching %s: %s\n", output, err)
os.Exit(1)
}
}
//
// Cleanup
//
if *cleanup {
os.Remove(path)
} else {
fmt.Printf("Generated output left at %s\n", path)
}
}