mirror of
https://github.com/RoBaertschi/tt.git
synced 2025-04-16 05:53:30 +00:00
- rename Architecture.md -> architecture.md - change error handling in typechecker to print the token loc for binary expressions and also not prefix the error when printed in cmd
186 lines
4.2 KiB
Go
186 lines
4.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"robaertschi.xyz/robaertschi/tt/asm/amd64"
|
|
"robaertschi.xyz/robaertschi/tt/lexer"
|
|
"robaertschi.xyz/robaertschi/tt/parser"
|
|
"robaertschi.xyz/robaertschi/tt/token"
|
|
"robaertschi.xyz/robaertschi/tt/ttir"
|
|
"robaertschi.xyz/robaertschi/tt/typechecker"
|
|
)
|
|
|
|
type ToPrintFlags int
|
|
|
|
const (
|
|
PrintAst ToPrintFlags = 1 << iota
|
|
PrintTAst
|
|
PrintIr
|
|
)
|
|
|
|
type Arguments struct {
|
|
Output string
|
|
Input string
|
|
OnlyEmitAsm bool
|
|
ToPrint ToPrintFlags
|
|
}
|
|
|
|
// Prefix writer writes a prefix before each new line from another io.Writer
|
|
type PrefixWriter struct {
|
|
output io.Writer
|
|
outputPrefix []byte
|
|
outputPrefixWritten bool
|
|
}
|
|
|
|
func NewPrefixWriter(output io.Writer, prefix []byte) *PrefixWriter {
|
|
return &PrefixWriter{
|
|
output: output,
|
|
outputPrefix: prefix,
|
|
}
|
|
}
|
|
|
|
func NewPrefixWriterString(output io.Writer, prefix string) *PrefixWriter {
|
|
return &PrefixWriter{
|
|
output: output,
|
|
outputPrefix: []byte(prefix),
|
|
}
|
|
}
|
|
|
|
func (w *PrefixWriter) Write(p []byte) (n int, err error) {
|
|
|
|
toWrites := bytes.SplitAfter(p, []byte{'\n'})
|
|
|
|
for _, toWrite := range toWrites {
|
|
if len(toWrite) <= 0 {
|
|
continue
|
|
}
|
|
if !w.outputPrefixWritten {
|
|
w.outputPrefixWritten = true
|
|
w.output.Write(w.outputPrefix)
|
|
}
|
|
|
|
if bytes.Contains(toWrite, []byte{'\n'}) {
|
|
w.outputPrefixWritten = false
|
|
}
|
|
|
|
var written int
|
|
written, err = w.output.Write(toWrite)
|
|
n += written
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func Compile(args Arguments) {
|
|
output := args.Output
|
|
input := args.Input
|
|
onlyEmitAsm := args.OnlyEmitAsm
|
|
|
|
asmOutputName := strings.TrimSuffix(input, filepath.Ext(input)) + ".asm"
|
|
|
|
file, err := os.Open(input)
|
|
if err != nil {
|
|
fmt.Printf("Could not open file %q because: %e", input, err)
|
|
os.Exit(1)
|
|
}
|
|
defer file.Close()
|
|
|
|
inputText, err := io.ReadAll(file)
|
|
if err != nil {
|
|
fmt.Printf("Could not read file %q because: %e", input, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
l, err := lexer.New(string(inputText), input)
|
|
if err != nil {
|
|
fmt.Printf("Error while creating lexer: %e", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
l.WithErrorCallback(func(l token.Loc, s string, a ...any) {
|
|
fmt.Printf("%s:%d:%d: %s\n", l.File, l.Line, l.Col, fmt.Sprintf(s, a...))
|
|
})
|
|
|
|
p := parser.New(l)
|
|
p.WithErrorCallback(func(t token.Token, s string, a ...any) {
|
|
loc := t.Loc
|
|
fmt.Printf("%s:%d:%d: %s\n", loc.File, loc.Line, loc.Col, fmt.Sprintf(s, a...))
|
|
})
|
|
|
|
program := p.ParseProgram()
|
|
if p.Errors() > 0 {
|
|
fmt.Printf("Parser encountered 1 or more errors, quiting...\n")
|
|
os.Exit(1)
|
|
}
|
|
if (args.ToPrint & PrintAst) != 0 {
|
|
fmt.Printf("AST:\n%s\n%+#v\n", program.String(), program)
|
|
}
|
|
|
|
tprogram, err := typechecker.New().CheckProgram(program)
|
|
if err != nil {
|
|
fmt.Printf("%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if (args.ToPrint & PrintTAst) != 0 {
|
|
fmt.Printf("TAST:\n%s\n%+#v\n", tprogram.String(), tprogram)
|
|
}
|
|
|
|
ir := ttir.EmitProgram(tprogram)
|
|
if (args.ToPrint & PrintIr) != 0 {
|
|
fmt.Printf("TTIR:\n%s\n%+#v\n", ir.String(), ir)
|
|
}
|
|
asm := amd64.CgProgram(ir)
|
|
|
|
asmOutput := asm.Emit()
|
|
|
|
asmOutputFile, err := os.Create(asmOutputName)
|
|
if err != nil {
|
|
fmt.Printf("Failed to create/truncate asm file %q because: %v\n", asmOutputName, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
_, err = asmOutputFile.WriteString(asmOutput)
|
|
asmOutputFile.Close()
|
|
if err != nil {
|
|
fmt.Printf("Failed to write to file %q because: %v\n", asmOutputName, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fasmPath, err := exec.LookPath("fasm")
|
|
if err != nil {
|
|
fasmPath, err = exec.LookPath("fasm2")
|
|
if err != nil {
|
|
fmt.Printf("Could not find fasm or fasm2, please install any those two using your systems package manager or from https://flatassembler.net\n")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
if !onlyEmitAsm {
|
|
args := []string{asmOutputName, output}
|
|
cmd := exec.Command(fasmPath, args...)
|
|
cmd.Stdout = NewPrefixWriterString(os.Stdout, "fasm output: ")
|
|
cmd.Stderr = cmd.Stdout
|
|
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
fmt.Printf("Failed to run fasm because: %v\nCheck the asm file %q for errors and report these to the author!\n", err, asmOutputName)
|
|
os.Exit(1)
|
|
}
|
|
|
|
removeErr := os.Remove(asmOutputName)
|
|
if removeErr != nil {
|
|
fmt.Printf("Failed to remove %q file, please remove it yourself. Err: %v\n", asmOutputName, err)
|
|
}
|
|
}
|
|
}
|