Reorganized file structure

This commit is contained in:
Eduard Urbach 2024-06-10 15:51:39 +02:00
parent c7354b8613
commit 6fe30f31da
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
57 changed files with 431 additions and 614 deletions

View File

@ -1,3 +1,3 @@
main() { main() {
print("Hello") syscall(60, 0)
} }

View File

@ -1,17 +0,0 @@
package register
import "git.akyoto.dev/cli/q/src/register"
const (
SyscallNumber = register.R8
SyscallReturn = register.R0
)
var SyscallArgs = []register.ID{
register.R0,
register.R1,
register.R2,
register.R3,
register.R4,
register.R5,
}

View File

@ -1,22 +0,0 @@
package x64
import "git.akyoto.dev/cli/q/src/register"
const (
SyscallNumber = register.R0 // rax
SyscallReturn = register.R0 // rax
)
var SyscallArgs = []register.ID{
register.R7, // rdi
register.R6, // rsi
register.R2, // rdx
register.R10,
register.R8,
register.R9,
}
// Syscall is the primary way to communicate with the OS kernel.
func Syscall(code []byte) []byte {
return append(code, 0x0f, 0x05)
}

View File

@ -1,4 +0,0 @@
package asm
// Address represents a memory address.
type Address = uint32

View File

@ -1,93 +0,0 @@
package asm
import (
"encoding/binary"
"git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/log"
"git.akyoto.dev/cli/q/src/register"
)
// Assembler contains a list of instructions.
type Assembler struct {
Instructions []Instruction
}
// New creates a new assembler.
func New() *Assembler {
return &Assembler{
Instructions: make([]Instruction, 0, 8),
}
}
// Finalize generates the final machine code.
func (a *Assembler) Finalize() ([]byte, []byte) {
code := make([]byte, 0, len(a.Instructions)*8)
data := make(Data, 0, 16)
pointers := []Pointer{}
for _, x := range a.Instructions {
switch x.Mnemonic {
case MOV:
code = x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number))
case MOVDATA:
code = x64.MoveRegNum32(code, uint8(x.Destination), 0)
pointers = append(pointers, Pointer{
Position: Address(len(code) - 4),
Address: data.Add(x.Data),
})
case SYSCALL:
code = x64.Syscall(code)
}
}
if config.Verbose {
for _, x := range a.Instructions {
log.Info.Println(x.String())
}
}
dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
for _, pointer := range pointers {
slice := code[pointer.Position : pointer.Position+4]
address := dataStart + pointer.Address
binary.LittleEndian.PutUint32(slice, address)
}
return code, data
}
// Merge combines the contents of this assembler with another one.
func (a *Assembler) Merge(b *Assembler) {
a.Instructions = append(a.Instructions, b.Instructions...)
}
// MoveRegisterData moves a data section address into the given register.
func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: MOVDATA,
Destination: reg,
Data: data,
})
}
// MoveRegisterNumber moves a number into the given register.
func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: MOV,
Destination: reg,
Number: number,
})
}
// Syscall executes a kernel function.
func (a *Assembler) Syscall() {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: SYSCALL,
})
}

View File

@ -1,41 +0,0 @@
package asm_test
import (
"testing"
"git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/os/linux"
"git.akyoto.dev/go/assert"
)
func TestHello(t *testing.T) {
a := asm.New()
hello := []byte("Hello\n")
a.MoveRegisterNumber(x64.SyscallNumber, linux.Write)
a.MoveRegisterNumber(x64.SyscallArgs[0], 1)
a.MoveRegisterData(x64.SyscallArgs[1], hello)
a.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(hello)))
a.Syscall()
a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit)
a.MoveRegisterNumber(x64.SyscallArgs[0], 0)
a.Syscall()
code, data := a.Finalize()
assert.DeepEqual(t, code, []byte{
0xb8, 0x01, 0x00, 0x00, 0x00,
0xbf, 0x01, 0x00, 0x00, 0x00,
0xbe, 0xa2, 0x00, 0x40, 0x00,
0xba, 0x06, 0x00, 0x00, 0x00,
0x0f, 0x05,
0xb8, 0x3c, 0x00, 0x00, 0x00,
0xbf, 0x00, 0x00, 0x00, 0x00,
0x0f, 0x05,
})
assert.DeepEqual(t, data, hello)
}

View File

@ -1,20 +0,0 @@
package asm
import "bytes"
// Data represents the static read-only data.
type Data []byte
// Add adds the given bytes to the data block if this sequence of bytes doesn't exist yet.
// It returns the address relative to the start of the data section.
func (data *Data) Add(block []byte) Address {
position := bytes.Index(*data, block)
if position != -1 {
return Address(position)
}
address := Address(len(*data))
*data = append(*data, block...)
return address
}

View File

@ -1,30 +0,0 @@
package asm
import (
"fmt"
"git.akyoto.dev/cli/q/src/register"
)
// Instruction represents a single instruction which can be converted to machine code.
type Instruction struct {
Mnemonic Mnemonic
Source register.ID
Destination register.ID
Number uint64
Data []byte
}
// String returns the assembler representation of the instruction.
func (x *Instruction) String() string {
switch x.Mnemonic {
case MOV:
return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number)
case MOVDATA:
return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data)
case SYSCALL:
return x.Mnemonic.String()
default:
return ""
}
}

View File

@ -1,12 +1,7 @@
package build package build
import ( import (
"bufio"
"os"
"path/filepath" "path/filepath"
"git.akyoto.dev/cli/q/src/compiler"
"git.akyoto.dev/cli/q/src/elf"
) )
// Build describes a compiler build. // Build describes a compiler build.
@ -25,7 +20,7 @@ func New(directory string) *Build {
// Run parses the input files and generates an executable file. // Run parses the input files and generates an executable file.
func (build *Build) Run() error { func (build *Build) Run() error {
functions, err := compiler.Compile(build.Directory) functions, err := Compile(build.Directory)
if err != nil { if err != nil {
return err return err
@ -35,33 +30,12 @@ func (build *Build) Run() error {
return nil return nil
} }
code, data := compiler.Finalize(functions) path := build.Executable()
return writeToDisk(build.Executable(), code, data) code, data := Finalize(functions)
return Write(path, code, data)
} }
// Executable returns the path to the executable. // Executable returns the path to the executable.
func (build *Build) Executable() string { func (build *Build) Executable() string {
return filepath.Join(build.Directory, filepath.Base(build.Directory)) return filepath.Join(build.Directory, filepath.Base(build.Directory))
} }
// writeToDisk writes the executable file to disk.
func writeToDisk(filePath string, code []byte, data []byte) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
buffer := bufio.NewWriter(file)
executable := elf.New(code, data)
executable.Write(buffer)
buffer.Flush()
err = file.Close()
if err != nil {
return err
}
return os.Chmod(filePath, 0755)
}

24
src/build/Build_test.go Normal file
View File

@ -0,0 +1,24 @@
package build_test
import (
"testing"
"git.akyoto.dev/cli/q/src/build"
"git.akyoto.dev/go/assert"
)
func TestBuild(t *testing.T) {
b := build.New("../../examples/hello")
assert.Nil(t, b.Run())
}
func TestSkipExecutable(t *testing.T) {
b := build.New("../../examples/hello")
b.WriteExecutable = false
assert.Nil(t, b.Run())
}
func TestNonExisting(t *testing.T) {
b := build.New("does-not-exist")
assert.NotNil(t, b.Run())
}

View File

@ -1,6 +1,8 @@
package compiler package build
import "sync" import (
"sync"
)
// Compile compiles all the functions. // Compile compiles all the functions.
func Compile(directory string) (map[string]*Function, error) { func Compile(directory string) (map[string]*Function, error) {

View File

@ -1,9 +1,7 @@
package compiler package build
import ( import (
"git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/os/linux"
) )
// Finalize generates the final machine code. // Finalize generates the final machine code.
@ -14,10 +12,6 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) {
a.Merge(&f.Assembler) a.Merge(&f.Assembler)
} }
a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit)
a.MoveRegisterNumber(x64.SyscallArgs[0], 0)
a.Syscall()
code, data := a.Finalize() code, data := a.Finalize()
return code, data return code, data
} }

96
src/build/Function.go Normal file
View File

@ -0,0 +1,96 @@
package build
import (
"fmt"
"strconv"
"git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/go/color/ansi"
)
// Function represents a function.
type Function struct {
Name string
Head token.List
Body token.List
Assembler asm.Assembler
}
// Compile turns a function into machine code.
func (f *Function) Compile() {
if config.Verbose {
ansi.Underline.Println(f.Name)
}
for _, line := range f.Lines() {
if config.Verbose {
fmt.Println("[line]", line)
}
if len(line) == 0 {
continue
}
if line[0].Kind == token.Identifier && line[0].Text() == "syscall" {
paramTokens := line[2 : len(line)-1]
start := 0
i := 0
var parameters []token.List
for i < len(paramTokens) {
if paramTokens[i].Kind == token.Separator {
parameters = append(parameters, paramTokens[start:i])
start = i + 1
}
i++
}
if i != start {
parameters = append(parameters, paramTokens[start:i])
}
for i, list := range parameters {
if list[0].Kind == token.Number {
numAsText := list[0].Text()
n, _ := strconv.Atoi(numAsText)
f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n))
}
}
f.Assembler.Syscall()
}
}
}
// Lines returns the lines in the function body.
func (f *Function) Lines() []token.List {
var (
lines []token.List
start = 0
i = 0
)
for i < len(f.Body) {
if f.Body[i].Kind == token.NewLine {
lines = append(lines, f.Body[start:i])
start = i + 1
}
i++
}
if i != start {
lines = append(lines, f.Body[start:i])
}
return lines
}
// String returns the function name.
func (f *Function) String() string {
return f.Name
}

View File

@ -1,4 +1,4 @@
package compiler package build
import ( import (
"os" "os"
@ -6,8 +6,8 @@ import (
"strings" "strings"
"sync" "sync"
"git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/build/directory"
"git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/build/token"
) )
// Scan scans the directory. // Scan scans the directory.

29
src/build/Write.go Normal file
View File

@ -0,0 +1,29 @@
package build
import (
"bufio"
"os"
"git.akyoto.dev/cli/q/src/build/elf"
)
// Write writes the executable file to disk.
func Write(filePath string, code []byte, data []byte) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
buffer := bufio.NewWriter(file)
executable := elf.New(code, data)
executable.Write(buffer)
buffer.Flush()
err = file.Close()
if err != nil {
return err
}
return os.Chmod(filePath, 0755)
}

View File

@ -0,0 +1,9 @@
package register
import "git.akyoto.dev/cli/q/src/build/cpu"
const (
SyscallReturn = 0
)
var SyscallArgs = []cpu.Register{8, 0, 1, 2, 3, 4, 5}

View File

@ -0,0 +1,14 @@
package x64
import "git.akyoto.dev/cli/q/src/build/cpu"
const (
SyscallReturn = 0 // rax
)
var SyscallArgs = []cpu.Register{0, 7, 6, 2, 10, 8, 9}
// Syscall is the primary way to communicate with the OS kernel.
func Syscall(code []byte) []byte {
return append(code, 0x0f, 0x05)
}

View File

@ -3,7 +3,7 @@ package x64_test
import ( import (
"testing" "testing"
"git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )

View File

@ -0,0 +1,66 @@
package asm
import (
"encoding/binary"
"fmt"
"git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/config"
)
// Assembler contains a list of instructions.
type Assembler struct {
Instructions []Instruction
}
// New creates a new assembler.
func New() *Assembler {
return &Assembler{
Instructions: make([]Instruction, 0, 8),
}
}
// Finalize generates the final machine code.
func (a *Assembler) Finalize() ([]byte, []byte) {
code := make([]byte, 0, len(a.Instructions)*8)
data := make([]byte, 0, 16)
pointers := []Pointer{}
for _, x := range a.Instructions {
switch x.Mnemonic {
case MOVE:
code = x64.MoveRegNum32(code, uint8(x.Data.(RegisterNumber).Register), uint32(x.Data.(RegisterNumber).Number))
if x.Data.(RegisterNumber).IsPointer {
pointers = append(pointers, Pointer{
Position: Address(len(code) - 4),
Address: Address(x.Data.(RegisterNumber).Number),
})
}
case SYSCALL:
code = x64.Syscall(code)
}
}
if config.Verbose {
for _, x := range a.Instructions {
fmt.Println("[asm]", x.String())
}
}
dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
for _, pointer := range pointers {
slice := code[pointer.Position : pointer.Position+4]
address := dataStart + pointer.Address
binary.LittleEndian.PutUint32(slice, address)
}
return code, data
}
// Merge combines the contents of this assembler with another one.
func (a *Assembler) Merge(b *Assembler) {
a.Instructions = append(a.Instructions, b.Instructions...)
}

View File

@ -0,0 +1,19 @@
package asm
import "fmt"
// Instruction represents a single instruction which can be converted to machine code.
type Instruction struct {
Mnemonic Mnemonic
Data interface{}
}
// String returns a human readable version.
func (x *Instruction) String() string {
switch data := x.Data.(type) {
case RegisterNumber:
return fmt.Sprintf("%s %s, %x", x.Mnemonic, data.Register, data.Number)
default:
return x.Mnemonic.String()
}
}

View File

@ -0,0 +1,32 @@
package asm
import "git.akyoto.dev/cli/q/src/build/cpu"
// MoveRegisterNumber moves a number into the given register.
func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: MOVE,
Data: RegisterNumber{
Register: reg,
Number: number,
IsPointer: false,
},
})
}
// MoveRegisterAddress moves an address into the given register.
func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: MOVE,
Data: RegisterNumber{
Register: reg,
Number: uint64(address),
IsPointer: true,
},
})
}
// Syscall executes a kernel function.
func (a *Assembler) Syscall() {
a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL})
}

View File

@ -4,18 +4,15 @@ type Mnemonic uint8
const ( const (
NONE Mnemonic = iota NONE Mnemonic = iota
MOV MOVE
MOVDATA
SYSCALL SYSCALL
) )
// String returns a human readable version.
func (m Mnemonic) String() string { func (m Mnemonic) String() string {
switch m { switch m {
case MOV: case MOVE:
return "mov" return "move"
case MOVDATA:
return "mov"
case SYSCALL: case SYSCALL:
return "syscall" return "syscall"

View File

@ -1,5 +1,8 @@
package asm package asm
// Address represents a memory address.
type Address = uint32
// Pointer stores a relative memory address that we can later turn into an absolute one. // Pointer stores a relative memory address that we can later turn into an absolute one.
// Position: The machine code offset where the address was inserted. // Position: The machine code offset where the address was inserted.
// Address: The offset inside the section. // Address: The offset inside the section.

View File

@ -0,0 +1,10 @@
package asm
import "git.akyoto.dev/cli/q/src/build/cpu"
// RegisterNumber operates with a register and a number.
type RegisterNumber struct {
Register cpu.Register
Number uint64
IsPointer bool
}

11
src/build/cpu/Register.go Normal file
View File

@ -0,0 +1,11 @@
package cpu
import "fmt"
// Register represents the number of the register.
type Register uint8
// String returns the human readable name of the register.
func (r Register) String() string {
return fmt.Sprintf("r%d", r)
}

View File

@ -3,7 +3,7 @@ package directory_test
import ( import (
"testing" "testing"
"git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/build/directory"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )

View File

@ -4,7 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/build/config"
) )
// ELF represents an ELF file. // ELF represents an ELF file.

View File

@ -4,7 +4,7 @@ import (
"io" "io"
"testing" "testing"
"git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/build/elf"
) )
func TestELF(t *testing.T) { func TestELF(t *testing.T) {

View File

@ -0,0 +1,27 @@
package output
import (
"fmt"
"git.akyoto.dev/cli/q/src/build"
"git.akyoto.dev/cli/q/src/build/token"
)
// Compiler implements the arch.Output interface.
type Compiler struct{}
// Compile turns a function into machine code.
func (c Compiler) Compile(f *build.Function) {
for i, t := range f.Body {
if t.Kind == token.Identifier && t.Text() == "print" {
// message := f.Body[i+2].Bytes
// f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write)
// f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1)
// f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message)
// f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message)))
// f.Assembler.Syscall()
message := f.Body[i+2].Bytes
fmt.Println(message)
}
}
}

View File

@ -3,7 +3,7 @@ package token_test
import ( import (
"testing" "testing"
"git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )
@ -96,6 +96,22 @@ func TestNewline(t *testing.T) {
}) })
} }
func TestNumber(t *testing.T) {
tokens := token.Tokenize([]byte(`123 -456`))
assert.DeepEqual(t, tokens, token.List{
{
Kind: token.Number,
Bytes: []byte("123"),
Position: 0,
},
{
Kind: token.Number,
Bytes: []byte("-456"),
Position: 4,
},
})
}
func TestSeparator(t *testing.T) { func TestSeparator(t *testing.T) {
tokens := token.Tokenize([]byte("a,b,c")) tokens := token.Tokenize([]byte("a,b,c"))
assert.DeepEqual(t, tokens, token.List{ assert.DeepEqual(t, tokens, token.List{

View File

@ -96,7 +96,25 @@ func Tokenize(buffer []byte) List {
} }
tokens = append(tokens, token) tokens = append(tokens, token)
i-- continue
}
// Numbers
if isNumberStart(buffer[i]) {
position := i
i++
for i < len(buffer) && isNumber(buffer[i]) {
i++
}
tokens = append(tokens, Token{
Number,
position,
buffer[position:i],
})
continue
} }
} }
@ -106,10 +124,22 @@ func Tokenize(buffer []byte) List {
return tokens return tokens
} }
func isIdentifierStart(c byte) bool { func isIdentifier(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' return isLetter(c) || isNumber(c) || c == '_'
} }
func isIdentifier(c byte) bool { func isIdentifierStart(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') return isLetter(c) || c == '_'
}
func isLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
func isNumber(c byte) bool {
return (c >= '0' && c <= '9')
}
func isNumberStart(c byte) bool {
return isNumber(c) || c == '-'
} }

View File

@ -1,11 +1,11 @@
package cli package cli
import ( import (
"fmt"
"strings" "strings"
"git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/build"
"git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/log"
) )
// Build builds an executable. // Build builds an executable.
@ -22,7 +22,7 @@ func Build(args []string) int {
default: default:
if strings.HasPrefix(args[i], "-") { if strings.HasPrefix(args[i], "-") {
log.Error.Printf("Unknown parameter: %s\n", args[i]) fmt.Printf("Unknown parameter: %s\n", args[i])
return 2 return 2
} }
@ -33,7 +33,7 @@ func Build(args []string) int {
err := b.Run() err := b.Run()
if err != nil { if err != nil {
log.Error.Println(err) fmt.Println(err)
return 1 return 1
} }

View File

@ -1,14 +1,12 @@
package cli package cli
import ( import "fmt"
"git.akyoto.dev/cli/q/src/log"
)
// Help shows the command line argument usage. // Help shows the command line argument usage.
func Help(args []string) int { func Help(args []string) int {
log.Error.Println("Usage: q [command] [options]") fmt.Println("Usage: q [command] [options]")
log.Error.Println("") fmt.Println("")
log.Error.Println(" build [directory]") fmt.Println(" build [directory]")
log.Error.Println(" system") fmt.Println(" system")
return 2 return 2
} }

View File

@ -2,21 +2,13 @@ package cli_test
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"testing" "testing"
"git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/cli"
"git.akyoto.dev/cli/q/src/log"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )
func TestMain(m *testing.M) {
log.Info.SetOutput(io.Discard)
log.Error.SetOutput(io.Discard)
os.Exit(m.Run())
}
func TestCLI(t *testing.T) { func TestCLI(t *testing.T) {
type cliTest struct { type cliTest struct {
arguments []string arguments []string

View File

@ -1,36 +1,35 @@
package cli package cli
import ( import (
"fmt"
"os" "os"
"runtime" "runtime"
"git.akyoto.dev/cli/q/src/log"
) )
// System shows system information. // System shows system information.
func System(args []string) int { func System(args []string) int {
line := "%-19s%s\n" line := "%-19s%s\n"
log.Info.Printf(line, "Platform:", runtime.GOOS) fmt.Printf(line, "Platform:", runtime.GOOS)
log.Info.Printf(line, "Architecture:", runtime.GOARCH) fmt.Printf(line, "Architecture:", runtime.GOARCH)
log.Info.Printf(line, "Go:", runtime.Version()) fmt.Printf(line, "Go:", runtime.Version())
// Directory // Directory
directory, err := os.Getwd() directory, err := os.Getwd()
if err == nil { if err == nil {
log.Info.Printf(line, "Directory:", directory) fmt.Printf(line, "Directory:", directory)
} else { } else {
log.Info.Printf(line, "Directory:", err.Error()) fmt.Printf(line, "Directory:", err.Error())
} }
// Compiler // Compiler
executable, err := os.Executable() executable, err := os.Executable()
if err == nil { if err == nil {
log.Info.Printf(line, "Compiler:", executable) fmt.Printf(line, "Compiler:", executable)
} else { } else {
log.Info.Printf(line, "Compiler:", err.Error()) fmt.Printf(line, "Compiler:", err.Error())
} }
return 0 return 0

View File

@ -1,35 +0,0 @@
package compiler
import (
"git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/os/linux"
"git.akyoto.dev/cli/q/src/token"
)
// Function represents a function.
type Function struct {
Name string
Head token.List
Body token.List
Assembler asm.Assembler
}
// Compile turns a function into machine code.
func (f *Function) Compile() {
for i, t := range f.Body {
if t.Kind == token.Identifier && t.Text() == "print" {
message := f.Body[i+2].Bytes
f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write)
f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1)
f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message)
f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message)))
f.Assembler.Syscall()
}
}
}
// String returns the function name.
func (f *Function) String() string {
return f.Name
}

View File

@ -1,106 +0,0 @@
package cpu
import "git.akyoto.dev/cli/q/src/register"
// CPU manages the allocation state of registers.
type CPU struct {
All List
General List
Call List
Syscall List
}
// New creates a new CPU state.
func New() *CPU {
// Rather than doing lots of mini allocations
// we'll allocate memory for all registers at once.
registers := [16]Register{
{ID: register.R0},
{ID: register.R1},
{ID: register.R2},
{ID: register.R3},
{ID: register.R4},
{ID: register.R5},
{ID: register.R6},
{ID: register.R7},
{ID: register.R8},
{ID: register.R9},
{ID: register.R10},
{ID: register.R11},
{ID: register.R12},
{ID: register.R13},
{ID: register.R14},
{ID: register.R15},
}
rax := &registers[0]
rcx := &registers[1]
rdx := &registers[2]
rbx := &registers[3]
rsp := &registers[4]
rbp := &registers[5]
rsi := &registers[6]
rdi := &registers[7]
r8 := &registers[8]
r9 := &registers[9]
r10 := &registers[10]
r11 := &registers[11]
r12 := &registers[12]
r13 := &registers[13]
r14 := &registers[14]
r15 := &registers[15]
// Register configuration
return &CPU{
All: List{
rax,
rcx,
rdx,
rbx,
rsp,
rbp,
rsi,
rdi,
r8,
r9,
r10,
r11,
r12,
r13,
r14,
r15,
},
General: List{
rcx,
rbx,
rbp,
r11,
r12,
r13,
r14,
r15,
},
Call: List{
rdi,
rsi,
rdx,
r10,
r8,
r9,
},
Syscall: List{
rax,
rdi,
rsi,
rdx,
r10,
r8,
r9,
},
}
}
// ByID returns the register with the given ID.
func (cpu *CPU) ByID(id register.ID) *Register {
return cpu.All[id]
}

View File

@ -1,29 +0,0 @@
package cpu
// List is a list of registers.
type List []*Register
// FindFree tries to find a free register
// and returns nil when all are currently occupied.
func (registers List) FindFree() *Register {
for _, register := range registers {
if register.IsFree() {
return register
}
}
return nil
}
// InUse returns a list of registers that are currently in use.
func (registers List) InUse() List {
var inUse List
for _, register := range registers {
if !register.IsFree() {
inUse = append(inUse, register)
}
}
return inUse
}

View File

@ -1,39 +0,0 @@
package cpu
import (
"fmt"
"git.akyoto.dev/cli/q/src/errors"
"git.akyoto.dev/cli/q/src/register"
)
// Register represents a single CPU register.
type Register struct {
ID register.ID
user fmt.Stringer
}
// Use marks the register as used by the given object.
func (register *Register) Use(obj fmt.Stringer) error {
if register.user != nil {
return &errors.RegisterInUse{Register: register.ID.String(), User: register.user.String()}
}
register.user = obj
return nil
}
// Free frees the register so that it can be used for new calculations.
func (register *Register) Free() {
register.user = nil
}
// IsFree returns true if the register is not in use.
func (register *Register) IsFree() bool {
return register.user == nil
}
// String returns a human-readable representation of the register.
func (register *Register) String() string {
return fmt.Sprintf("%s%s%v", register.ID, "=", register.user)
}

View File

@ -1,17 +0,0 @@
package errors
import "fmt"
// InvalidDirectory errors are returned when the specified path is not a directory.
type InvalidDirectory struct {
Path string
}
// Error implements the text representation.
func (err *InvalidDirectory) Error() string {
if err.Path == "" {
return "Invalid directory"
}
return fmt.Sprintf("Invalid directory '%s'", err.Path)
}

View File

@ -1,14 +0,0 @@
package errors
import "fmt"
// RegisterInUse errors are returned when a register is already in use.
type RegisterInUse struct {
Register string
User string
}
// Error implements the text representation.
func (err *RegisterInUse) Error() string {
return fmt.Sprintf("Register '%s' already used by '%s'", err.Register, err.User)
}

View File

@ -1,14 +0,0 @@
package log
import (
"log"
"os"
)
var (
// Info is used for general info messages.
Info = log.New(os.Stdout, "", 0)
// Error is used for error messages.
Error = log.New(os.Stderr, "", 0)
)

View File

@ -1,44 +0,0 @@
package register
import "fmt"
// ID represents the number of the register.
type ID uint8
const (
R0 ID = iota
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
R11
R12
R13
R14
R15
R16
R17
R18
R19
R20
R21
R22
R23
R24
R25
R26
R27
R28
R29
R30
)
func (r ID) String() string {
return fmt.Sprintf("r%d", r)
}