From dbaa77aa1bde2b536571af89b11ac15234c8f3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20B=C3=A4rtschi?= Date: Tue, 28 Jan 2025 14:29:08 +0100 Subject: [PATCH] begin term lib for build system --- ast/binaryoperator_string.go | 10 +- build/build.go | 17 ++-- build/task.go | 87 +++++++++++----- main.go | 6 +- term/term.go | 186 +++++++++++++++++++++++++++++++++++ utils/level_string.go | 27 +++++ utils/utils.go | 122 +++++++++++++++++++++++ 7 files changed, 420 insertions(+), 35 deletions(-) create mode 100644 term/term.go create mode 100644 utils/level_string.go diff --git a/ast/binaryoperator_string.go b/ast/binaryoperator_string.go index 58dbd4f..b347004 100644 --- a/ast/binaryoperator_string.go +++ b/ast/binaryoperator_string.go @@ -12,11 +12,17 @@ func _() { _ = x[Subtract-1] _ = x[Multiply-2] _ = x[Divide-3] + _ = x[Equal-4] + _ = x[NotEqual-5] + _ = x[LessThan-6] + _ = x[LessThanEqual-7] + _ = x[GreaterThan-8] + _ = x[GreaterThanEqual-9] } -const _BinaryOperator_name = "AddSubtractMultiplyDivide" +const _BinaryOperator_name = "AddSubtractMultiplyDivideEqualNotEqualLessThanLessThanEqualGreaterThanGreaterThanEqual" -var _BinaryOperator_index = [...]uint8{0, 3, 11, 19, 25} +var _BinaryOperator_index = [...]uint8{0, 3, 11, 19, 25, 30, 38, 46, 59, 70, 86} func (i BinaryOperator) String() string { if i < 0 || i >= BinaryOperator(len(_BinaryOperator_index)-1) { diff --git a/build/build.go b/build/build.go index 28b0f66..822dad9 100644 --- a/build/build.go +++ b/build/build.go @@ -3,13 +3,14 @@ package build import ( "fmt" - "log" + "io" "os" "os/exec" "path/filepath" "strings" "robaertschi.xyz/robaertschi/tt/asm" + "robaertschi.xyz/robaertschi/tt/utils" ) type ToPrintFlags int @@ -38,14 +39,14 @@ func NewSourceProgram(inputFile string, outputFile string) *SourceProgram { } func (sp *SourceProgram) Build(backend asm.Backend, emitAsmOnly bool, toPrint ToPrintFlags) error { - l := log.New(os.Stderr, "[build] ", log.Lshortfile) + l := utils.NewLogger(os.Stderr, "[build] ", utils.Info) nodes := make(map[int]*node) rootNodes := []int{} id := 0 addRootNode := func(task task) int { - l.Printf("registering root task %d", id) + l.Debugf("registering root task %d", id) node := &node{task: task} nodes[id] = node rootNodes = append(rootNodes, id) @@ -54,7 +55,7 @@ func (sp *SourceProgram) Build(backend asm.Backend, emitAsmOnly bool, toPrint To } addNode := func(task task, deps ...int) int { - l.Printf("registering task %d", id) + l.Debugf("registering task %d", id) if len(deps) <= 0 { panic("node without dep is useless") } @@ -91,12 +92,14 @@ func (sp *SourceProgram) buildFasm(addRootNode func(task) int, addNode func(task mainAsmOutput := strings.TrimSuffix(sp.InputFile, filepath.Ext(sp.InputFile)) + ".asm" - asmFile := addRootNode(NewFuncTask(func() error { - return build(sp.InputFile, mainAsmOutput, toPrint) + asmFile := addRootNode(NewFuncTask("generating assembly for "+sp.InputFile, func(output io.Writer) error { + return build(output, sp.InputFile, mainAsmOutput, toPrint) })) if !emitAsmOnly { - fasmTask := addNode(NewProcessTask(fasmPath, mainAsmOutput, sp.OutputFile), asmFile) + task := NewProcessTask(fasmPath, mainAsmOutput, sp.OutputFile) + task.WithName("assembling " + mainAsmOutput) + fasmTask := addNode(task, asmFile) // Cleanup diff --git a/build/task.go b/build/task.go index 8c79600..fe9fc74 100644 --- a/build/task.go +++ b/build/task.go @@ -4,10 +4,10 @@ import ( "errors" "fmt" "io" - "log" "os" "os/exec" "slices" + "strings" "robaertschi.xyz/robaertschi/tt/asm/amd64" "robaertschi.xyz/robaertschi/tt/lexer" @@ -19,27 +19,35 @@ import ( ) type task interface { - Run(id int, doneChan chan taskResult) + Run(id int, output io.Writer, doneChan chan taskResult) + Name() string + WithName(string) } type processTask struct { - name string - args []string + taskName string + name string + args []string } func NewProcessTask(name string, args ...string) task { return &processTask{ - name: name, - args: args, + taskName: name, + name: name, + args: args, } } -func (pt *processTask) Run(id int, doneChan chan taskResult) { +func (pt *processTask) WithName(name string) { + pt.taskName = name +} + +func (pt *processTask) Run(id int, output io.Writer, doneChan chan taskResult) { cmd := exec.Command(pt.name, pt.args...) - cmd.Stdout = utils.NewPrefixWriterString(os.Stdout, pt.name+" output: ") + cmd.Stdout = utils.NewPrefixWriterString(output, pt.name+" output: ") cmd.Stderr = cmd.Stdout - fmt.Printf("starting %q %v\n", pt.name, pt.args) + io.WriteString(output, fmt.Sprintf("starting %q %v\n", pt.name, pt.args)) err := cmd.Run() var exitError error if cmd.ProcessState.ExitCode() != 0 { @@ -51,15 +59,20 @@ func (pt *processTask) Run(id int, doneChan chan taskResult) { } } +func (pt *processTask) Name() string { + return pt.taskName +} + type removeFileTask struct { file string + name string } func NewRemoveFileTask(file string) task { - return &removeFileTask{file: file} + return &removeFileTask{file: file, name: fmt.Sprintf("removing file %q", file)} } -func (rft *removeFileTask) Run(id int, doneChan chan taskResult) { +func (rft *removeFileTask) Run(id int, output io.Writer, doneChan chan taskResult) { err := os.Remove(rft.file) doneChan <- taskResult{ Id: id, @@ -67,22 +80,35 @@ func (rft *removeFileTask) Run(id int, doneChan chan taskResult) { } } +func (rft *removeFileTask) Name() string { return rft.name } + +func (rft *removeFileTask) WithName(name string) { rft.name = name } + type funcTask struct { - f func() error + f func(io.Writer) error + name string } -func NewFuncTask(f func() error) task { - return &funcTask{f: f} +func NewFuncTask(taskName string, f func(io.Writer) error) task { + return &funcTask{f: f, name: taskName} } -func (rft *funcTask) Run(id int, doneChan chan taskResult) { +func (rft *funcTask) Run(id int, output io.Writer, doneChan chan taskResult) { doneChan <- taskResult{ Id: id, - Err: rft.f(), + Err: rft.f(output), } } -func build(input string, output string, toPrint ToPrintFlags) error { +func (rft *funcTask) Name() string { + return rft.name +} + +func (rft *funcTask) WithName(name string) { + rft.name = name +} + +func build(outputWriter io.Writer, input string, output string, toPrint ToPrintFlags) error { file, err := os.Open(input) if err != nil { return fmt.Errorf("could not open file %q because: %v", input, err) @@ -114,7 +140,8 @@ func build(input string, output string, toPrint ToPrintFlags) error { return fmt.Errorf("parser encountered 1 or more errors") } if (toPrint & PrintAst) != 0 { - fmt.Printf("AST:\n%s\n%+#v\n", program.String(), program) + io.WriteString(outputWriter, + fmt.Sprintf("AST:\n%s\n%+#v\n", program.String(), program)) } tprogram, err := typechecker.New().CheckProgram(program) @@ -122,12 +149,14 @@ func build(input string, output string, toPrint ToPrintFlags) error { return err } if (toPrint & PrintTAst) != 0 { - fmt.Printf("TAST:\n%s\n%+#v\n", tprogram.String(), tprogram) + io.WriteString(outputWriter, + fmt.Sprintf("TAST:\n%s\n%+#v\n", tprogram.String(), tprogram)) } ir := ttir.EmitProgram(tprogram) if (toPrint & PrintIr) != 0 { - fmt.Printf("TTIR:\n%s\n%+#v\n", ir.String(), ir) + io.WriteString(outputWriter, + fmt.Sprintf("TTIR:\n%s\n%+#v\n", ir.String(), ir)) } asm := amd64.CgProgram(ir) @@ -167,9 +196,10 @@ const ( failed ) -func runTasks(nodes map[int]*node, rootNodes []int, l *log.Logger) error { +func runTasks(nodes map[int]*node, rootNodes []int, l *utils.Logger) error { done := make(map[int]executionState) + output := make(map[int]*strings.Builder) running := []int{} doneChan := make(chan taskResult) errs := []error{} @@ -178,9 +208,10 @@ func runTasks(nodes map[int]*node, rootNodes []int, l *log.Logger) error { if done[id] != notStarted { panic(fmt.Sprintf("tried starting task %d twice", id)) } - // fmt.Printf("executing task %d\n", id) + l.Debugf("executing task %d", id) node := nodes[id] - go node.task.Run(id, doneChan) + output[id] = &strings.Builder{} + go node.task.Run(id, output[id], doneChan) running = append(running, id) done[id] = executing } @@ -206,7 +237,7 @@ func runTasks(nodes map[int]*node, rootNodes []int, l *log.Logger) error { } } - // fmt.Printf("starting rootNodes %v\n", rootNodes) + l.Debugf("starting rootNodes %v", rootNodes) for _, rootNode := range rootNodes { startTask(rootNode) } @@ -216,7 +247,7 @@ func runTasks(nodes map[int]*node, rootNodes []int, l *log.Logger) error { for !allFinished { select { case result := <-doneChan: - // fmt.Printf("task %d is done with err: %v\n", result.Id, result.Err) + l.Debugf("task %d is done with err: %v", result.Id, result.Err) for i, id := range running { if id == result.Id { running = slices.Delete(running, i, i+1) @@ -252,6 +283,12 @@ func runTasks(nodes map[int]*node, rootNodes []int, l *log.Logger) error { } } + for id, node := range nodes { + if output[id].Len() > 0 { + l.Infof("task %q output: %s", node.task.Name(), output[id]) + } + } + return errors.Join(errs...) } diff --git a/main.go b/main.go index 78b649f..977ef7a 100644 --- a/main.go +++ b/main.go @@ -10,9 +10,13 @@ import ( "robaertschi.xyz/robaertschi/tt/asm" "robaertschi.xyz/robaertschi/tt/build" + "robaertschi.xyz/robaertschi/tt/term" ) func main() { + r, c, err := term.GetCursorPosition() + fmt.Printf("%d, %d, %v\n", r, c, err) + flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [flags] input\nPossible flags:\n", os.Args[0]) flag.PrintDefaults() @@ -51,7 +55,7 @@ func main() { logger := log.New(os.Stderr, "", log.Lshortfile) - err := build.NewSourceProgram(input, output).Build(asm.Fasm, *emitAsmOnly, build.ToPrintFlags(toPrint)) + err = build.NewSourceProgram(input, output).Build(asm.Fasm, *emitAsmOnly, build.ToPrintFlags(toPrint)) if err != nil { logger.Fatalln(err) os.Exit(1) diff --git a/term/term.go b/term/term.go new file mode 100644 index 0000000..3fff53c --- /dev/null +++ b/term/term.go @@ -0,0 +1,186 @@ +package term + +import ( + "errors" + "fmt" + "internal/syscall/unix" + "os" +) + +const ESC = "\x1b" +const CSI = ESC + "[" +const Reset = CSI + "0m" + +// Colors + +const ( + BlackFg = "30" + BlackBg = "40" + RedFg = "31" + RedBg = "41" + GreenFg = "32" + GreenBg = "42" + YellowFg = "33" + YellowBg = "43" + BlueFg = "34" + BlueBg = "44" + MagentaFg = "35" + MagentaBg = "35" + CyanFg = "36" + CyanBg = "46" + WhiteFg = "37" + WhiteBg = "47" +) + +func Color(col string) string { + return CSI + col + "m" +} + +// Other CSI + +func CursorUp(amount int) string { + return fmt.Sprintf("%s%dA", CSI, amount) +} + +func CursorDown(amount int) string { + return fmt.Sprintf("%s%dB", CSI, amount) +} + +func CursorForward(amount int) string { + return fmt.Sprintf("%s%dC", CSI, amount) +} + +func CursorBack(amount int) string { + return fmt.Sprintf("%s%dD", CSI, amount) +} + +// Not ANSI.SYS +func CursorNextLine(amount int) string { + return fmt.Sprintf("%s%dE", CSI, amount) +} + +// Not ANSI.SYS +func CursorPreviousLine(amount int) string { + return fmt.Sprintf("%s%dF", CSI, amount) +} + +// Not ANSI.SYS +// Moves cursor to column amount +func CursorHorizontalAbsolute(amount int) string { + return fmt.Sprintf("%s%dG", CSI, amount) +} + +// Moves the cursor on the 1-based grid in the terminal +func CursorPostition(x, y int) string { + return fmt.Sprintf("%s%d;%dH", CSI, x, y) +} + +type EraseInDisplayMode int + +const ( + ClearFromCursor EraseInDisplayMode = iota // clear from cursor to the end of the screen + ClearToCursor // clear to cursor from the begining of the screen + ClearEntireScreen + ClearEntireScreenAndScrollbackBuffer // xterm extension +) + +func EraseInDisplay(mode EraseInDisplayMode) string { + return fmt.Sprintf("%s%dJ", CSI, mode) +} + +type EraseInLineMode int + +const ( + FromCursorToEnd EraseInLineMode = iota + FromCursorToBegin + EntireLine +) + +func EraseInLine(mode EraseInLineMode) string { + return fmt.Sprintf("%s%dK", CSI, mode) +} + +// Not ANSI.SYS +func ScrollUp(amount int) string { + return fmt.Sprintf("%s%dS", CSI, amount) +} + +// Not ANSI.SYS +func ScrollDown(amount int) string { + return fmt.Sprintf("%s%dT", CSI, amount) +} + +// Moves the cursor on the 1-based grid in the terminal +// Same as Cursor Position but a format effector, not a editor function +// see https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands +func HorizontalVerticalPosition(x, y int) string { + return fmt.Sprintf("%s%d;%dH", CSI, x, y) +} + +// Gets the cursor position by transmitting CSIn;mR n = row, m = column +// see https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands +// Use GetCursorPosition to get x and y +func DeviceStatusReport() string { + return CSI + "6n" +} + +var DidNotGetCsi = errors.New("could not get csi from Device Status Report sequence") + +func GetCursorPosition() (row, column int, err error) { + // _, err = os.Stdin.Seek(0, 2) + // if err != nil { + // return + // } + os.Stdout.Write([]byte(DeviceStatusReport())) + // CSI is ESC and [ + csiBuffer := [2]byte{} + os.Stdin.Read(csiBuffer[:]) + + if string(csiBuffer[:]) != CSI { + err = DidNotGetCsi + return + } + + miniBuff := [1]byte{} + for { + _, err = os.Stdin.Read(miniBuff[:]) + + if err != nil { + return + } + + if miniBuff[0] == ';' { + break + } + + if '0' <= miniBuff[0] && miniBuff[0] <= '9' { + row *= 10 + row += int(miniBuff[0] - '0') + } else { + err = fmt.Errorf("invalid byte for number %b", miniBuff[0]) + return + } + } + + for { + _, err = os.Stdin.Read(miniBuff[:]) + + if err != nil { + return + } + + if miniBuff[0] == 'R' { + break + } + + if '0' <= miniBuff[0] && miniBuff[0] <= '9' { + column *= 10 + column += int(miniBuff[0] - '0') + } else { + err = fmt.Errorf("invalid byte for number %b", miniBuff[0]) + return + } + } + + return +} diff --git a/utils/level_string.go b/utils/level_string.go new file mode 100644 index 0000000..99b16be --- /dev/null +++ b/utils/level_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -type=Level -linecomment"; DO NOT EDIT. + +package utils + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Debug-0] + _ = x[Info-1] + _ = x[Warn-2] + _ = x[Error-3] + _ = x[Fatal-4] +} + +const _Level_name = "DEBUGINFOWARNERRORFATAL" + +var _Level_index = [...]uint8{0, 5, 9, 13, 18, 23} + +func (i Level) String() string { + if i < 0 || i >= Level(len(_Level_index)-1) { + return "Level(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Level_name[_Level_index[i]:_Level_index[i+1]] +} diff --git a/utils/utils.go b/utils/utils.go index 5598fcd..3d30e06 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,7 +2,13 @@ package utils import ( "bytes" + "fmt" "io" + "os" + "strings" + "sync" + + "robaertschi.xyz/robaertschi/tt/term" ) // Prefix writer writes a prefix before each new line from another io.Writer @@ -53,3 +59,119 @@ func (w *PrefixWriter) Write(p []byte) (n int, err error) { return } + +//go:generate stringer -type=Level -linecomment +type Level int + +const ( + Debug Level = iota // DEBUG + Info // INFO + Warn // WARN + Error // ERROR + Fatal // FATAL +) + +type LoggerFormatFunc func(prefix string, level Level, msg string) string + +type Logger struct { + outMu sync.Mutex + out io.Writer + prefix string + format LoggerFormatFunc + filter Level +} + +func DefaultLoggerFormatFunc(prefix string, level Level, msg string) string { + + colorString := "" + switch level { + case Debug: + colorString = term.CSI + "90m" + case Info: + case Warn: + colorString = term.Color(term.YellowFg) + case Error: + colorString = term.Color(term.RedFg) + case Fatal: + colorString = term.CSI + term.WhiteFg + term.RedBg + "m" + } + + return fmt.Sprintf("%s%s[%s] %s%s", colorString, prefix, level, msg, term.Reset) +} + +// filter filters anything below that level out, it does not stop a os.Exit() from a fatal +func NewLogger(output io.Writer, prefix string, filter Level) *Logger { + l := new(Logger) + l.SetPrefix(prefix) + l.SetOutput(output) + l.format = DefaultLoggerFormatFunc + l.filter = filter + return l +} + +func (l *Logger) SetPrefix(prefix string) { + l.prefix = prefix +} + +func (l *Logger) SetOutput(output io.Writer) { + // NOTE(Robin): Do some research/testing if we need to look the mutex for this + l.out = output +} + +func (l *Logger) Msg(level Level, msg string) { + if level >= l.filter { + l.outMu.Lock() + result := l.format(l.prefix, level, strings.TrimRight(msg, " \n\t")) + io.WriteString(l.out, result) + io.WriteString(l.out, "\n") + l.outMu.Unlock() + } + + if level == Fatal { + os.Exit(1) + } +} + +func (l *Logger) Msgf(level Level, msg string, args ...any) { + l.Msg(level, fmt.Sprintf(msg, args...)) +} + +func (l *Logger) Debug(msg string) { + l.Msg(Debug, msg) +} + +func (l *Logger) Debugf(msg string, args ...any) { + l.Msgf(Debug, msg, args...) +} + +func (l *Logger) Info(msg string) { + l.Msg(Info, msg) +} + +func (l *Logger) Infof(msg string, args ...any) { + l.Msgf(Info, msg, args...) +} + +func (l *Logger) Warn(msg string) { + l.Msg(Warn, msg) +} + +func (l *Logger) Warnf(msg string, args ...any) { + l.Msgf(Warn, msg, args...) +} + +func (l *Logger) Error(msg string) { + l.Msg(Error, msg) +} + +func (l *Logger) Errorf(msg string, args ...any) { + l.Msgf(Error, msg, args...) +} + +func (l *Logger) Fatal(msg string) { + l.Msg(Fatal, msg) +} + +func (l *Logger) Fatalf(msg string, args ...any) { + l.Msgf(Fatal, msg, args...) +}