diff --git a/asm/backend.go b/asm/backend.go new file mode 100644 index 0000000..da73ab3 --- /dev/null +++ b/asm/backend.go @@ -0,0 +1,8 @@ +package asm + +type Backend string + +const ( + Fasm Backend = "fasm" + Qbe Backend = "qbe" +) diff --git a/asm/qbe/qbe.go b/asm/qbe/qbe.go new file mode 100644 index 0000000..954de33 --- /dev/null +++ b/asm/qbe/qbe.go @@ -0,0 +1 @@ +package qbe diff --git a/asm/qbe/qbe_stub.asm b/asm/qbe/qbe_stub.asm new file mode 100644 index 0000000..02ff633 --- /dev/null +++ b/asm/qbe/qbe_stub.asm @@ -0,0 +1,30 @@ +; Reference: https://filippo.io/linux-syscall-table/ +; https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ +; https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions +format ELF64 + + public syscall1 + public syscall2 + public syscall3 + ; rdi => Syscall number, rsi => argument +syscall1: + mov rax, rdi + mov rdi, rsi + syscall + ret + +syscall2: + mov rax, rdi + mov rdi, rsi + mov rsi, rdx + syscall + ret + + +syscall3: + mov rax, rdi + mov rdi, rsi + mov rsi, rdx + mov rdx, rcx + syscall + ret diff --git a/build/build.go b/build/build.go new file mode 100644 index 0000000..3aebb50 --- /dev/null +++ b/build/build.go @@ -0,0 +1,143 @@ +// Build allows the user to build a tt file. The goal is to make it easy to support multiple backends with different requirements +package build + +import ( + "errors" + "fmt" + "os" + "os/exec" + + "robaertschi.xyz/robaertschi/tt/asm" + "robaertschi.xyz/robaertschi/tt/utils" +) + +type SourceProgram struct { + // The tt source file + InputFile string + // A list of additional assembly files to compile + // This file could be extended by different backends + // .asm is for fasm, .S for gas + InputAssemblies []string + // Additional object files, will also include the generated object files + ObjectFiles []string + // The linkded executable + OutputFile string +} + +type task interface { + Run(id int, doneChan chan taskResult) +} + +type processTask struct { + name string + args []string +} + +func NewProcessTask(id int, name string, args ...string) task { + return &processTask{ + name: name, + args: args, + } +} + +func (pt *processTask) Run(id int, doneChan chan taskResult) { + cmd := exec.Command(pt.name, pt.args...) + cmd.Stdout = utils.NewPrefixWriterString(os.Stdout, pt.name+" output: ") + cmd.Stderr = cmd.Stdout + + err := cmd.Run() + var exitError error + if cmd.ProcessState.ExitCode() != 0 { + exitError = fmt.Errorf("command %q failed with exit code %d", pt.name, cmd.ProcessState.ExitCode()) + } + doneChan <- taskResult{ + Id: id, + Err: errors.Join(err, exitError), + } +} + +type removeFileTask struct { + file string +} + +func NewRemoveFileTask(file string) task { + return &removeFileTask{file: file} +} + +func (rft *removeFileTask) Run(id int, doneChan chan taskResult) { + doneChan <- taskResult{ + Id: id, + Err: os.Remove(rft.file), + } +} + +type taskResult struct { + Id int + Err error +} + +type node struct { + next *int + previous []int + task task +} + +func runTasks(nodes map[int]*node, rootNodes []int) { + done := make(map[int]bool) + running := []int{} + doneChan := make(chan taskResult) + + for id, node := range nodes { + // Initalize map + done[id] = false + + // because we are already going trough the whole map, we might as well + // check the relations + if node.next != nil { + if _, ok := nodes[*node.next]; !ok { + panic(fmt.Sprintf("task with id %d has a invalid next node", id)) + } + } + + for _, prev := range node.previous { + if _, ok := nodes[prev]; !ok { + panic(fmt.Sprintf("task with id %d has a invalid prev node", id)) + } + } + } + + for _, rootNode := range rootNodes { + node := nodes[rootNode] + go node.task.Run(rootNode, doneChan) + running = append(running, rootNode) + } + + for { + select { + case result := <-doneChan: + done[result.Id] = true + for i, id := range running { + if id == result.Id { + running = make([]int, len(running)-1) + running = append(running[:i], running[i+1:]...) + } + } + + node := nodes[result.Id] + if node.next != nil { + allDone := true + for _, prev := range node.previous { + if !done[prev] { + allDone = false + break + } + } + } + } + } + +} + +func (sp *SourceProgram) Build(backend asm.Backend) { + +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 3c1026a..0718cb4 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,7 +1,6 @@ package cmd import ( - "bytes" "fmt" "io" "os" @@ -15,6 +14,7 @@ import ( "robaertschi.xyz/robaertschi/tt/token" "robaertschi.xyz/robaertschi/tt/ttir" "robaertschi.xyz/robaertschi/tt/typechecker" + "robaertschi.xyz/robaertschi/tt/utils" ) type ToPrintFlags int @@ -32,55 +32,6 @@ type Arguments struct { 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 @@ -168,7 +119,7 @@ func Compile(args Arguments) { if !onlyEmitAsm { args := []string{asmOutputName, output} cmd := exec.Command(fasmPath, args...) - cmd.Stdout = NewPrefixWriterString(os.Stdout, "fasm output: ") + cmd.Stdout = utils.NewPrefixWriterString(os.Stdout, "fasm output: ") cmd.Stderr = cmd.Stdout err = cmd.Run() diff --git a/language.md b/language.md index b96f49c..7b92a69 100644 --- a/language.md +++ b/language.md @@ -9,3 +9,44 @@ fn main() = { i }; ``` + +## Reference + +### Basic Datatypes + +#### Numbers + +There is currently only one number type, `i64`, `i64` is a signed integer of the size of 64 bits. + +#### Booleans + +The boolean type `bool` can be either true or false, nothing else, it's size is implementation dependend and is only guaranteed to be 1 bit big. + +### Expressions + +There are many types of expression, tt is expression oriented. + +#### Integer Expression + +A Integer Expression contains an untyped, non-floating point, integer. +```tt +1234567890 +100000 +``` +The Integer Expression must at minimum support the largest number type. + +#### Boolean Expression +Is either the keyword `true` or `false`. +```tt +true +false +``` + +#### Binary Expression + +A Binary Expression is a expression with two expression and an operator between them. A Operator has a precedence, that deteirmines, which way they have to be parsed. + +##### Operators +- `+` Adds two numbers with the same type together +- `-` Subtracts the left expression with the right expression, they have the same type +- `*` diff --git a/utils/utils.go b/utils/utils.go index d4b585b..5598fcd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1 +1,55 @@ package utils + +import ( + "bytes" + "io" +) + +// 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 +}