Separated code and data
This commit is contained in:
parent
aab33fe86d
commit
0fe419da86
@ -2,6 +2,7 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -11,61 +12,62 @@ import (
|
|||||||
|
|
||||||
// Build describes a compiler build.
|
// Build describes a compiler build.
|
||||||
type Build struct {
|
type Build struct {
|
||||||
ExecutableName string
|
Name string
|
||||||
ExecutablePath string
|
Directory string
|
||||||
|
Code bytes.Buffer
|
||||||
|
Data bytes.Buffer
|
||||||
WriteExecutable bool
|
WriteExecutable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new build.
|
// New creates a new build.
|
||||||
func New(directory string) (*Build, error) {
|
func New(directory string) *Build {
|
||||||
directory, err := filepath.Abs(directory)
|
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 {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
files, err := file.Readdirnames(0)
|
files, err := file.Readdirnames(0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range files {
|
for _, name := range files {
|
||||||
log.Info.Println(name)
|
log.Info.Println(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
executableName := filepath.Base(directory)
|
build.Code.Write([]byte{
|
||||||
|
|
||||||
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{
|
|
||||||
0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
|
0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
|
||||||
0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1
|
0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1
|
||||||
0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2
|
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
|
0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60
|
||||||
0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0
|
0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0
|
||||||
0x0f, 0x05, // syscall
|
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.
|
// 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)
|
file, err := os.Create(filePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,7 +92,7 @@ func writeToDisk(filePath string, code []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buffer := bufio.NewWriter(file)
|
buffer := bufio.NewWriter(file)
|
||||||
executable := elf.New(code)
|
executable := elf.New(code, data)
|
||||||
executable.Write(buffer)
|
executable.Write(buffer)
|
||||||
buffer.Flush()
|
buffer.Flush()
|
||||||
|
|
||||||
|
@ -10,16 +10,17 @@ const (
|
|||||||
baseAddress = 0x40 * minAddress
|
baseAddress = 0x40 * minAddress
|
||||||
)
|
)
|
||||||
|
|
||||||
// ELF64 represents an ELF 64-bit file.
|
// ELF represents an ELF file.
|
||||||
type ELF64 struct {
|
type ELF struct {
|
||||||
Header
|
Header
|
||||||
ProgramHeader
|
ProgramHeader
|
||||||
Code []byte
|
Code []byte
|
||||||
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new 64-bit ELF binary.
|
// New creates a new ELF binary.
|
||||||
func New(code []byte) *ELF64 {
|
func New(code []byte, data []byte) *ELF {
|
||||||
elf := &ELF64{
|
elf := &ELF{
|
||||||
Header: Header{
|
Header: Header{
|
||||||
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
|
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
|
||||||
Class: 2,
|
Class: 2,
|
||||||
@ -43,7 +44,7 @@ func New(code []byte) *ELF64 {
|
|||||||
},
|
},
|
||||||
ProgramHeader: ProgramHeader{
|
ProgramHeader: ProgramHeader{
|
||||||
Type: ProgramTypeLOAD,
|
Type: ProgramTypeLOAD,
|
||||||
Flags: ProgramFlagsExecutable | ProgramFlagsReadable,
|
Flags: ProgramFlagsExecutable,
|
||||||
Offset: 0x80,
|
Offset: 0x80,
|
||||||
VirtualAddress: baseAddress + 0x80,
|
VirtualAddress: baseAddress + 0x80,
|
||||||
PhysicalAddress: baseAddress + 0x80,
|
PhysicalAddress: baseAddress + 0x80,
|
||||||
@ -52,15 +53,17 @@ func New(code []byte) *ELF64 {
|
|||||||
Align: Align,
|
Align: Align,
|
||||||
},
|
},
|
||||||
Code: code,
|
Code: code,
|
||||||
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
return elf
|
return elf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the ELF64 format to the given writer.
|
// 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.Header)
|
||||||
binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader)
|
binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader)
|
||||||
writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
|
writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
|
||||||
writer.Write(elf.Code)
|
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"
|
"git.akyoto.dev/cli/q/cli/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Build builds an executable.
|
||||||
func Build(args []string) int {
|
func Build(args []string) int {
|
||||||
directory := "."
|
directory := "."
|
||||||
|
|
||||||
@ -12,12 +13,7 @@ func Build(args []string) int {
|
|||||||
directory = args[0]
|
directory = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := build.New(directory)
|
b := build.New(directory)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error.Println(err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < len(args); i++ {
|
for i := 1; i < len(args); i++ {
|
||||||
switch args[i] {
|
switch args[i] {
|
||||||
@ -30,7 +26,7 @@ func Build(args []string) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.Run()
|
err := b.Run()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error.Println(err)
|
log.Error.Println(err)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"git.akyoto.dev/cli/q/cli/log"
|
"git.akyoto.dev/cli/q/cli/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Help shows the command line argument usage.
|
||||||
func Help(args []string) int {
|
func Help(args []string) int {
|
||||||
log.Error.Println("Usage: q [command] [options]")
|
log.Error.Println("Usage: q [command] [options]")
|
||||||
log.Error.Println("")
|
log.Error.Println("")
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package cli
|
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 {
|
func Main(args []string) int {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return Help(nil)
|
return Help(nil)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"git.akyoto.dev/cli/q/cli/log"
|
"git.akyoto.dev/cli/q/cli/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// System shows system information.
|
||||||
func System(args []string) int {
|
func System(args []string) int {
|
||||||
line := "%-19s%s\n"
|
line := "%-19s%s\n"
|
||||||
|
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
package main_test
|
package cli_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/cli"
|
"git.akyoto.dev/cli/q/cli"
|
||||||
|
"git.akyoto.dev/cli/q/cli/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
|
||||||
@ -28,3 +37,11 @@ func TestCLI(t *testing.T) {
|
|||||||
assert.Equal(t, exitCode, test.expectedExitCode)
|
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