diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 4f723f0..76d8d21 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,3 @@ main() { - print("Hello") + syscall(60, 0) } diff --git a/src/arch/arm64/Syscall.go b/src/arch/arm64/Syscall.go deleted file mode 100644 index 37e0b9e..0000000 --- a/src/arch/arm64/Syscall.go +++ /dev/null @@ -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, -} diff --git a/src/arch/x64/Syscall.go b/src/arch/x64/Syscall.go deleted file mode 100644 index 132e560..0000000 --- a/src/arch/x64/Syscall.go +++ /dev/null @@ -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) -} diff --git a/src/asm/Address.go b/src/asm/Address.go deleted file mode 100644 index 14b073b..0000000 --- a/src/asm/Address.go +++ /dev/null @@ -1,4 +0,0 @@ -package asm - -// Address represents a memory address. -type Address = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go deleted file mode 100644 index 5de2ff9..0000000 --- a/src/asm/Assembler.go +++ /dev/null @@ -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, - }) -} diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go deleted file mode 100644 index ed47f32..0000000 --- a/src/asm/Assembler_test.go +++ /dev/null @@ -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) -} diff --git a/src/asm/Data.go b/src/asm/Data.go deleted file mode 100644 index 418a898..0000000 --- a/src/asm/Data.go +++ /dev/null @@ -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 -} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go deleted file mode 100644 index 0c91ee1..0000000 --- a/src/asm/Instruction.go +++ /dev/null @@ -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 "" - } -} diff --git a/src/build/Build.go b/src/build/Build.go index f73045a..c7029cb 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -1,12 +1,7 @@ package build import ( - "bufio" - "os" "path/filepath" - - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/elf" ) // Build describes a compiler build. @@ -25,7 +20,7 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - functions, err := compiler.Compile(build.Directory) + functions, err := Compile(build.Directory) if err != nil { return err @@ -35,33 +30,12 @@ func (build *Build) Run() error { return nil } - code, data := compiler.Finalize(functions) - return writeToDisk(build.Executable(), code, data) + path := build.Executable() + code, data := Finalize(functions) + return Write(path, code, data) } // Executable returns the path to the executable. func (build *Build) Executable() string { 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) -} diff --git a/src/build/Build_test.go b/src/build/Build_test.go new file mode 100644 index 0000000..7c91f0b --- /dev/null +++ b/src/build/Build_test.go @@ -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()) +} diff --git a/src/compiler/Compile.go b/src/build/Compile.go similarity index 94% rename from src/compiler/Compile.go rename to src/build/Compile.go index 5311d01..1d1ffc1 100644 --- a/src/compiler/Compile.go +++ b/src/build/Compile.go @@ -1,6 +1,8 @@ -package compiler +package build -import "sync" +import ( + "sync" +) // Compile compiles all the functions. func Compile(directory string) (map[string]*Function, error) { diff --git a/src/compiler/Finalize.go b/src/build/Finalize.go similarity index 51% rename from src/compiler/Finalize.go rename to src/build/Finalize.go index 56095f4..413ce71 100644 --- a/src/compiler/Finalize.go +++ b/src/build/Finalize.go @@ -1,9 +1,7 @@ -package compiler +package build 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/build/asm" ) // Finalize generates the final machine code. @@ -14,10 +12,6 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { a.Merge(&f.Assembler) } - a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) - a.MoveRegisterNumber(x64.SyscallArgs[0], 0) - a.Syscall() - code, data := a.Finalize() return code, data } diff --git a/src/build/Function.go b/src/build/Function.go new file mode 100644 index 0000000..11c84ed --- /dev/null +++ b/src/build/Function.go @@ -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 +} diff --git a/src/compiler/Scan.go b/src/build/Scan.go similarity index 94% rename from src/compiler/Scan.go rename to src/build/Scan.go index d0ef625..ae24220 100644 --- a/src/compiler/Scan.go +++ b/src/build/Scan.go @@ -1,4 +1,4 @@ -package compiler +package build import ( "os" @@ -6,8 +6,8 @@ import ( "strings" "sync" - "git.akyoto.dev/cli/q/src/directory" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/token" ) // Scan scans the directory. diff --git a/src/build/Write.go b/src/build/Write.go new file mode 100644 index 0000000..f6385bb --- /dev/null +++ b/src/build/Write.go @@ -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) +} diff --git a/src/build/arch/arm64/Syscall.go b/src/build/arch/arm64/Syscall.go new file mode 100644 index 0000000..fb6495b --- /dev/null +++ b/src/build/arch/arm64/Syscall.go @@ -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} diff --git a/src/arch/x64/Call.go b/src/build/arch/x64/Call.go similarity index 100% rename from src/arch/x64/Call.go rename to src/build/arch/x64/Call.go diff --git a/src/arch/x64/Move.go b/src/build/arch/x64/Move.go similarity index 100% rename from src/arch/x64/Move.go rename to src/build/arch/x64/Move.go diff --git a/src/arch/x64/Return.go b/src/build/arch/x64/Return.go similarity index 100% rename from src/arch/x64/Return.go rename to src/build/arch/x64/Return.go diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go new file mode 100644 index 0000000..6251de9 --- /dev/null +++ b/src/build/arch/x64/Syscall.go @@ -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) +} diff --git a/src/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go similarity index 91% rename from src/arch/x64/x64_test.go rename to src/build/arch/x64/x64_test.go index 8fb7c21..b39570d 100644 --- a/src/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go new file mode 100644 index 0000000..94a4ce7 --- /dev/null +++ b/src/build/asm/Assembler.go @@ -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...) +} diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go new file mode 100644 index 0000000..966ce75 --- /dev/null +++ b/src/build/asm/Instruction.go @@ -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() + } +} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go new file mode 100644 index 0000000..0cd9014 --- /dev/null +++ b/src/build/asm/Instructions.go @@ -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}) +} diff --git a/src/asm/Mnemonic.go b/src/build/asm/Mnemonic.go similarity index 69% rename from src/asm/Mnemonic.go rename to src/build/asm/Mnemonic.go index 3d3a16c..cd7f033 100644 --- a/src/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,18 +4,15 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota - MOV - MOVDATA + MOVE SYSCALL ) +// String returns a human readable version. func (m Mnemonic) String() string { switch m { - case MOV: - return "mov" - - case MOVDATA: - return "mov" + case MOVE: + return "move" case SYSCALL: return "syscall" diff --git a/src/asm/Pointer.go b/src/build/asm/Pointer.go similarity index 81% rename from src/asm/Pointer.go rename to src/build/asm/Pointer.go index 5d13b4f..7063ec5 100644 --- a/src/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -1,5 +1,8 @@ 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. // Position: The machine code offset where the address was inserted. // Address: The offset inside the section. diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go new file mode 100644 index 0000000..445acd1 --- /dev/null +++ b/src/build/asm/RegisterNumber.go @@ -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 +} diff --git a/src/config/config.go b/src/build/config/config.go similarity index 100% rename from src/config/config.go rename to src/build/config/config.go diff --git a/src/build/cpu/Register.go b/src/build/cpu/Register.go new file mode 100644 index 0000000..006fc68 --- /dev/null +++ b/src/build/cpu/Register.go @@ -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) +} diff --git a/src/directory/Walk.go b/src/build/directory/Walk.go similarity index 100% rename from src/directory/Walk.go rename to src/build/directory/Walk.go diff --git a/src/directory/Walk_test.go b/src/build/directory/Walk_test.go similarity index 90% rename from src/directory/Walk_test.go rename to src/build/directory/Walk_test.go index f3f64cf..00c663f 100644 --- a/src/directory/Walk_test.go +++ b/src/build/directory/Walk_test.go @@ -3,7 +3,7 @@ package directory_test import ( "testing" - "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/go/assert" ) diff --git a/src/elf/ELF.go b/src/build/elf/ELF.go similarity index 97% rename from src/elf/ELF.go rename to src/build/elf/ELF.go index 92d45c6..f284dbc 100644 --- a/src/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/build/config" ) // ELF represents an ELF file. diff --git a/src/elf/ELF_test.go b/src/build/elf/ELF_test.go similarity index 77% rename from src/elf/ELF_test.go rename to src/build/elf/ELF_test.go index 45a9aa2..b609d60 100644 --- a/src/elf/ELF_test.go +++ b/src/build/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/build/elf" ) func TestELF(t *testing.T) { diff --git a/src/elf/Header.go b/src/build/elf/Header.go similarity index 100% rename from src/elf/Header.go rename to src/build/elf/Header.go diff --git a/src/elf/ProgramHeader.go b/src/build/elf/ProgramHeader.go similarity index 100% rename from src/elf/ProgramHeader.go rename to src/build/elf/ProgramHeader.go diff --git a/src/elf/SectionHeader.go b/src/build/elf/SectionHeader.go similarity index 100% rename from src/elf/SectionHeader.go rename to src/build/elf/SectionHeader.go diff --git a/src/elf/elf.md b/src/build/elf/elf.md similarity index 100% rename from src/elf/elf.md rename to src/build/elf/elf.md diff --git a/src/os/linux/Syscall.go b/src/build/os/linux/Syscall.go similarity index 100% rename from src/os/linux/Syscall.go rename to src/build/os/linux/Syscall.go diff --git a/src/build/output/Compiler.go b/src/build/output/Compiler.go new file mode 100644 index 0000000..dbbff06 --- /dev/null +++ b/src/build/output/Compiler.go @@ -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) + } + } +} diff --git a/src/token/Keywords.go b/src/build/token/Keywords.go similarity index 100% rename from src/token/Keywords.go rename to src/build/token/Keywords.go diff --git a/src/token/Kind.go b/src/build/token/Kind.go similarity index 100% rename from src/token/Kind.go rename to src/build/token/Kind.go diff --git a/src/token/List.go b/src/build/token/List.go similarity index 100% rename from src/token/List.go rename to src/build/token/List.go diff --git a/src/token/Token.go b/src/build/token/Token.go similarity index 100% rename from src/token/Token.go rename to src/build/token/Token.go diff --git a/src/token/Token_test.go b/src/build/token/Token_test.go similarity index 92% rename from src/token/Token_test.go rename to src/build/token/Token_test.go index 8bcac13..1bc125e 100644 --- a/src/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/build/token" "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) { tokens := token.Tokenize([]byte("a,b,c")) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/token/Tokenize.go b/src/build/token/Tokenize.go similarity index 78% rename from src/token/Tokenize.go rename to src/build/token/Tokenize.go index 4692791..d0402e3 100644 --- a/src/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -96,7 +96,25 @@ func Tokenize(buffer []byte) List { } 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 } -func isIdentifierStart(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +func isIdentifier(c byte) bool { + return isLetter(c) || isNumber(c) || c == '_' } -func isIdentifier(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') +func isIdentifierStart(c byte) bool { + 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 == '-' } diff --git a/src/cli/Build.go b/src/cli/Build.go index 2974257..2eb6cf2 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,11 +1,11 @@ package cli import ( + "fmt" "strings" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/build/config" ) // Build builds an executable. @@ -22,7 +22,7 @@ func Build(args []string) int { default: if strings.HasPrefix(args[i], "-") { - log.Error.Printf("Unknown parameter: %s\n", args[i]) + fmt.Printf("Unknown parameter: %s\n", args[i]) return 2 } @@ -33,7 +33,7 @@ func Build(args []string) int { err := b.Run() if err != nil { - log.Error.Println(err) + fmt.Println(err) return 1 } diff --git a/src/cli/Help.go b/src/cli/Help.go index 35fa8cd..6ca3b3c 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -1,14 +1,12 @@ package cli -import ( - "git.akyoto.dev/cli/q/src/log" -) +import "fmt" // Help shows the command line argument usage. func Help(args []string) int { - log.Error.Println("Usage: q [command] [options]") - log.Error.Println("") - log.Error.Println(" build [directory]") - log.Error.Println(" system") + fmt.Println("Usage: q [command] [options]") + fmt.Println("") + fmt.Println(" build [directory]") + fmt.Println(" system") return 2 } diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 95fcf55..f5ae0a4 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -2,21 +2,13 @@ package cli_test import ( "fmt" - "io" "os" "testing" "git.akyoto.dev/cli/q/src/cli" - "git.akyoto.dev/cli/q/src/log" "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) { type cliTest struct { arguments []string diff --git a/src/cli/System.go b/src/cli/System.go index d76f565..831576c 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -1,36 +1,35 @@ package cli import ( + "fmt" "os" "runtime" - - "git.akyoto.dev/cli/q/src/log" ) // System shows system information. func System(args []string) int { line := "%-19s%s\n" - log.Info.Printf(line, "Platform:", runtime.GOOS) - log.Info.Printf(line, "Architecture:", runtime.GOARCH) - log.Info.Printf(line, "Go:", runtime.Version()) + fmt.Printf(line, "Platform:", runtime.GOOS) + fmt.Printf(line, "Architecture:", runtime.GOARCH) + fmt.Printf(line, "Go:", runtime.Version()) // Directory directory, err := os.Getwd() if err == nil { - log.Info.Printf(line, "Directory:", directory) + fmt.Printf(line, "Directory:", directory) } else { - log.Info.Printf(line, "Directory:", err.Error()) + fmt.Printf(line, "Directory:", err.Error()) } // Compiler executable, err := os.Executable() if err == nil { - log.Info.Printf(line, "Compiler:", executable) + fmt.Printf(line, "Compiler:", executable) } else { - log.Info.Printf(line, "Compiler:", err.Error()) + fmt.Printf(line, "Compiler:", err.Error()) } return 0 diff --git a/src/compiler/Function.go b/src/compiler/Function.go deleted file mode 100644 index d9e65a3..0000000 --- a/src/compiler/Function.go +++ /dev/null @@ -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 -} diff --git a/src/cpu/CPU.go b/src/cpu/CPU.go deleted file mode 100644 index ea16af0..0000000 --- a/src/cpu/CPU.go +++ /dev/null @@ -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 := ®isters[0] - rcx := ®isters[1] - rdx := ®isters[2] - rbx := ®isters[3] - rsp := ®isters[4] - rbp := ®isters[5] - rsi := ®isters[6] - rdi := ®isters[7] - r8 := ®isters[8] - r9 := ®isters[9] - r10 := ®isters[10] - r11 := ®isters[11] - r12 := ®isters[12] - r13 := ®isters[13] - r14 := ®isters[14] - r15 := ®isters[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] -} diff --git a/src/cpu/List.go b/src/cpu/List.go deleted file mode 100644 index e9234aa..0000000 --- a/src/cpu/List.go +++ /dev/null @@ -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 -} diff --git a/src/cpu/Register.go b/src/cpu/Register.go deleted file mode 100644 index b11add9..0000000 --- a/src/cpu/Register.go +++ /dev/null @@ -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) -} diff --git a/src/errors/InvalidDirectory.go b/src/errors/InvalidDirectory.go deleted file mode 100644 index 90beadd..0000000 --- a/src/errors/InvalidDirectory.go +++ /dev/null @@ -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) -} diff --git a/src/errors/RegisterInUse.go b/src/errors/RegisterInUse.go deleted file mode 100644 index 27f40a5..0000000 --- a/src/errors/RegisterInUse.go +++ /dev/null @@ -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) -} diff --git a/src/log/log.go b/src/log/log.go deleted file mode 100644 index 395f6ab..0000000 --- a/src/log/log.go +++ /dev/null @@ -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) -) diff --git a/src/register/ID.go b/src/register/ID.go deleted file mode 100644 index bc2fe1c..0000000 --- a/src/register/ID.go +++ /dev/null @@ -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) -}