diff --git a/build/Build.go b/build/Build.go index fbc2a56..c9c6943 100644 --- a/build/Build.go +++ b/build/Build.go @@ -2,6 +2,7 @@ package build import ( "bufio" + "bytes" "os" "path/filepath" @@ -11,61 +12,62 @@ import ( // Build describes a compiler build. type Build struct { - ExecutableName string - ExecutablePath string + Name string + Directory string + Code bytes.Buffer + Data bytes.Buffer WriteExecutable bool } // New creates a new build. -func New(directory string) (*Build, error) { - directory, err := filepath.Abs(directory) +func New(directory string) *Build { + return &Build{ + Name: filepath.Base(directory), + Directory: directory, + WriteExecutable: true, + } +} + +// Run parses the input files and generates an executable file. +func (build *Build) Run() error { + err := build.Compile() if err != nil { - return nil, err + return err } - file, err := os.Open(directory) + if build.WriteExecutable { + return writeToDisk(build.Executable(), build.Code.Bytes(), build.Data.Bytes()) + } + + return nil +} + +// Executable returns the path to the executable. +func (build *Build) Executable() string { + return filepath.Join(build.Directory, build.Name) +} + +// Compile compiles all the functions. +func (build *Build) Compile() error { + file, err := os.Open(build.Directory) if err != nil { - return nil, err + return err } defer file.Close() files, err := file.Readdirnames(0) if err != nil { - return nil, err + return err } for _, name := range files { log.Info.Println(name) } - executableName := filepath.Base(directory) - - build := &Build{ - ExecutableName: executableName, - ExecutablePath: filepath.Join(directory, executableName), - WriteExecutable: true, - } - - return build, nil -} - -// Run parses the input files and generates an executable file. -func (build *Build) Run() error { - code := build.Compile() - - if build.WriteExecutable { - return writeToDisk(build.ExecutablePath, code) - } - - return nil -} - -// Compile compiles all the functions. -func (build *Build) Compile() []byte { - return []byte{ + build.Code.Write([]byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 @@ -75,13 +77,14 @@ func (build *Build) Compile() []byte { 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 0x0f, 0x05, // syscall + }) - 'H', 'e', 'l', 'l', 'o', '\n', - } + build.Data.Write([]byte{'H', 'e', 'l', 'l', 'o', '\n'}) + return nil } // writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte) error { +func writeToDisk(filePath string, code []byte, data []byte) error { file, err := os.Create(filePath) if err != nil { @@ -89,7 +92,7 @@ func writeToDisk(filePath string, code []byte) error { } buffer := bufio.NewWriter(file) - executable := elf.New(code) + executable := elf.New(code, data) executable.Write(buffer) buffer.Flush() diff --git a/build/elf/elf.go b/build/elf/ELF64.go similarity index 84% rename from build/elf/elf.go rename to build/elf/ELF64.go index 22e800e..30c1ce0 100644 --- a/build/elf/elf.go +++ b/build/elf/ELF64.go @@ -10,16 +10,17 @@ const ( baseAddress = 0x40 * minAddress ) -// ELF64 represents an ELF 64-bit file. -type ELF64 struct { +// ELF represents an ELF file. +type ELF struct { Header ProgramHeader Code []byte + Data []byte } -// New creates a new 64-bit ELF binary. -func New(code []byte) *ELF64 { - elf := &ELF64{ +// New creates a new ELF binary. +func New(code []byte, data []byte) *ELF { + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -43,7 +44,7 @@ func New(code []byte) *ELF64 { }, ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable | ProgramFlagsReadable, + Flags: ProgramFlagsExecutable, Offset: 0x80, VirtualAddress: baseAddress + 0x80, PhysicalAddress: baseAddress + 0x80, @@ -52,15 +53,17 @@ func New(code []byte) *ELF64 { Align: Align, }, Code: code, + Data: data, } return elf } // Write writes the ELF64 format to the given writer. -func (elf *ELF64) Write(writer io.Writer) { +func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) writer.Write(elf.Code) + writer.Write(elf.Data) } diff --git a/cli/Build.go b/cli/Build.go index e93d442..b579dbf 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// Build builds an executable. func Build(args []string) int { directory := "." @@ -12,12 +13,7 @@ func Build(args []string) int { directory = args[0] } - b, err := build.New(directory) - - if err != nil { - log.Error.Println(err) - return 1 - } + b := build.New(directory) for i := 1; i < len(args); i++ { switch args[i] { @@ -30,7 +26,7 @@ func Build(args []string) int { } } - err = b.Run() + err := b.Run() if err != nil { log.Error.Println(err) diff --git a/cli/Help.go b/cli/Help.go index 73559c9..61ba523 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// Help shows the command line argument usage. func Help(args []string) int { log.Error.Println("Usage: q [command] [options]") log.Error.Println("") diff --git a/cli/Main.go b/cli/Main.go index d6d43c6..e681239 100644 --- a/cli/Main.go +++ b/cli/Main.go @@ -1,5 +1,8 @@ package cli +// Main is the entry point for the CLI frontend. +// It returns the exit code of the compiler. +// We never call os.Exit directly here because it's bad for testing. func Main(args []string) int { if len(args) == 0 { return Help(nil) diff --git a/cli/System.go b/cli/System.go index 538a178..1b847aa 100644 --- a/cli/System.go +++ b/cli/System.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// System shows system information. func System(args []string) int { line := "%-19s%s\n" diff --git a/cli_test.go b/cli/cli_test.go similarity index 65% rename from cli_test.go rename to cli/cli_test.go index 376ddd1..c1e5861 100644 --- a/cli_test.go +++ b/cli/cli_test.go @@ -1,12 +1,21 @@ -package main_test +package cli_test import ( + "io" + "os" "testing" "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/cli/q/cli/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 @@ -28,3 +37,11 @@ func TestCLI(t *testing.T) { assert.Equal(t, exitCode, test.expectedExitCode) } } + +func BenchmarkBuild(b *testing.B) { + args := []string{"build", "examples/hello", "--dry"} + + for i := 0; i < b.N; i++ { + cli.Main(args) + } +} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index d05e9a6..0000000 --- a/main_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package main_test - -import ( - "io" - "os" - "testing" - - "git.akyoto.dev/cli/q/cli/log" -) - -func TestMain(m *testing.M) { - log.Info.SetOutput(io.Discard) - log.Error.SetOutput(io.Discard) - os.Exit(m.Run()) -}