Separated code and data
This commit is contained in:
parent
aab33fe86d
commit
0fe419da86
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
10
cli/Build.go
10
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)
|
||||
|
@ -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("")
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
15
main_test.go
15
main_test.go
@ -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())
|
||||
}
|
Loading…
Reference in New Issue
Block a user