Separated compiler into its own package
This commit is contained in:
parent
c19ad24428
commit
724794b4aa
@ -4,7 +4,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/core"
|
"git.akyoto.dev/cli/q/src/build/compiler"
|
||||||
"git.akyoto.dev/cli/q/src/build/scanner"
|
"git.akyoto.dev/cli/q/src/build/scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ func New(files ...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() (core.Result, error) {
|
func (build *Build) Run() (compiler.Result, error) {
|
||||||
functions, errors := scanner.Scan(build.Files)
|
functions, errors := scanner.Scan(build.Files)
|
||||||
return core.Compile(functions, errors)
|
return compiler.Compile(functions, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executable returns the path to the executable.
|
// Executable returns the path to the executable.
|
||||||
|
75
src/build/compiler/Compile.go
Normal file
75
src/build/compiler/Compile.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/core"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compile waits for the scan to finish and compiles all functions.
|
||||||
|
func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) {
|
||||||
|
result := Result{}
|
||||||
|
all := map[string]*core.Function{}
|
||||||
|
|
||||||
|
for functions != nil || errs != nil {
|
||||||
|
select {
|
||||||
|
case err, ok := <-errs:
|
||||||
|
if !ok {
|
||||||
|
errs = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
|
||||||
|
case function, ok := <-functions:
|
||||||
|
if !ok {
|
||||||
|
functions = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
function.Functions = all
|
||||||
|
all[function.Name] = function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start parallel compilation
|
||||||
|
CompileFunctions(all)
|
||||||
|
|
||||||
|
// Report errors if any occurred
|
||||||
|
for _, function := range all {
|
||||||
|
if function.Err != nil {
|
||||||
|
return result, function.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.InstructionCount += len(function.Assembler.Instructions)
|
||||||
|
result.DataCount += len(function.Assembler.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for existence of `main`
|
||||||
|
main, exists := all["main"]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return result, errors.MissingMainFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Main = main
|
||||||
|
result.Functions = all
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileFunctions starts a goroutine for each function compilation and waits for completion.
|
||||||
|
func CompileFunctions(functions map[string]*core.Function) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
for _, function := range functions {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
function.Compile()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -6,15 +6,17 @@ import (
|
|||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/core"
|
||||||
"git.akyoto.dev/cli/q/src/build/elf"
|
"git.akyoto.dev/cli/q/src/build/elf"
|
||||||
"git.akyoto.dev/cli/q/src/build/os/linux"
|
"git.akyoto.dev/cli/q/src/build/os/linux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result contains all the compiled functions in a build.
|
// Result contains all the compiled functions in a build.
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Main *Function
|
Main *core.Function
|
||||||
Functions map[string]*Function
|
Functions map[string]*core.Function
|
||||||
InstructionCount int
|
InstructionCount int
|
||||||
|
DataCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
// finalize generates the final machine code.
|
// finalize generates the final machine code.
|
||||||
@ -25,7 +27,7 @@ func (r *Result) finalize() ([]byte, []byte) {
|
|||||||
// a return address on the stack, which allows return statements in `main`.
|
// a return address on the stack, which allows return statements in `main`.
|
||||||
final := asm.Assembler{
|
final := asm.Assembler{
|
||||||
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
|
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
|
||||||
Data: map[string][]byte{},
|
Data: make(map[string][]byte, r.DataCount),
|
||||||
}
|
}
|
||||||
|
|
||||||
final.Call("main")
|
final.Call("main")
|
||||||
@ -35,8 +37,8 @@ func (r *Result) finalize() ([]byte, []byte) {
|
|||||||
|
|
||||||
// This will place the main function immediately after the entry point
|
// This will place the main function immediately after the entry point
|
||||||
// and also add everything the main function calls recursively.
|
// and also add everything the main function calls recursively.
|
||||||
r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) {
|
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
|
||||||
final.Merge(f.assembler)
|
final.Merge(f.Assembler)
|
||||||
})
|
})
|
||||||
|
|
||||||
code, data := final.Finalize()
|
code, data := final.Finalize()
|
||||||
@ -45,11 +47,11 @@ func (r *Result) finalize() ([]byte, []byte) {
|
|||||||
|
|
||||||
// eachFunction recursively finds all the calls to external functions.
|
// eachFunction recursively finds all the calls to external functions.
|
||||||
// It avoids calling the same function twice with the help of a hashmap.
|
// It avoids calling the same function twice with the help of a hashmap.
|
||||||
func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) {
|
func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) {
|
||||||
call(caller)
|
call(caller)
|
||||||
traversed[caller] = true
|
traversed[caller] = true
|
||||||
|
|
||||||
for _, x := range caller.assembler.Instructions {
|
for _, x := range caller.Assembler.Instructions {
|
||||||
if x.Mnemonic != asm.CALL {
|
if x.Mnemonic != asm.CALL {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -71,7 +73,7 @@ func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, ca
|
|||||||
|
|
||||||
// PrintInstructions prints out the generated instructions.
|
// PrintInstructions prints out the generated instructions.
|
||||||
func (r *Result) PrintInstructions() {
|
func (r *Result) PrintInstructions() {
|
||||||
r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) {
|
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
|
||||||
f.PrintInstructions()
|
f.PrintInstructions()
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -1,55 +1,66 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/build/ast"
|
||||||
"git.akyoto.dev/cli/q/src/build/errors"
|
"git.akyoto.dev/cli/q/src/build/errors"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compile waits for the scan to finish and compiles all functions.
|
// Compile turns a function into machine code.
|
||||||
func Compile(functions <-chan *Function, errs <-chan error) (Result, error) {
|
func (f *Function) Compile() {
|
||||||
result := Result{}
|
defer close(f.finished)
|
||||||
allFunctions := map[string]*Function{}
|
f.AddLabel(f.Name)
|
||||||
|
f.Err = f.CompileTokens(f.Body)
|
||||||
for functions != nil || errs != nil {
|
f.Return()
|
||||||
select {
|
|
||||||
case err, ok := <-errs:
|
|
||||||
if !ok {
|
|
||||||
errs = nil
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, err
|
// CompileTokens compiles a token list.
|
||||||
|
func (f *Function) CompileTokens(tokens token.List) error {
|
||||||
|
body, err := ast.Parse(tokens)
|
||||||
|
|
||||||
case function, ok := <-functions:
|
if err != nil {
|
||||||
if !ok {
|
err.(*errors.Error).File = f.File
|
||||||
functions = nil
|
return err
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function.functions = allFunctions
|
return f.CompileAST(body)
|
||||||
allFunctions[function.Name] = function
|
}
|
||||||
|
|
||||||
|
// CompileAST compiles an abstract syntax tree.
|
||||||
|
func (f *Function) CompileAST(tree ast.AST) error {
|
||||||
|
for _, node := range tree {
|
||||||
|
err := f.CompileASTNode(node)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start parallel compilation
|
return nil
|
||||||
CompileAllFunctions(allFunctions)
|
|
||||||
|
|
||||||
// Report errors if any occurred
|
|
||||||
for _, function := range allFunctions {
|
|
||||||
if function.err != nil {
|
|
||||||
return result, function.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.InstructionCount += len(function.assembler.Instructions)
|
// CompileASTNode compiles a node in the AST.
|
||||||
}
|
func (f *Function) CompileASTNode(node ast.Node) error {
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *ast.Assign:
|
||||||
|
return f.CompileAssign(node)
|
||||||
|
|
||||||
// Check for existence of `main`
|
case *ast.Call:
|
||||||
main, exists := allFunctions["main"]
|
return f.CompileCall(node.Expression)
|
||||||
|
|
||||||
if !exists {
|
case *ast.Define:
|
||||||
return result, errors.MissingMainFunction
|
return f.CompileDefinition(node)
|
||||||
}
|
|
||||||
|
|
||||||
result.Main = main
|
case *ast.Return:
|
||||||
result.Functions = allFunctions
|
return f.CompileReturn(node)
|
||||||
return result, nil
|
|
||||||
|
case *ast.If:
|
||||||
|
return f.CompileIf(node)
|
||||||
|
|
||||||
|
case *ast.Loop:
|
||||||
|
return f.CompileLoop(node)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unknown AST type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
// CompileAllFunctions starts a goroutine for each function compilation and waits for completion.
|
|
||||||
func CompileAllFunctions(functions map[string]*Function) {
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
|
|
||||||
for _, function := range functions {
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
function.Compile()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
@ -15,7 +15,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
|||||||
isSyscall := funcName == "syscall"
|
isSyscall := funcName == "syscall"
|
||||||
|
|
||||||
if !isSyscall {
|
if !isSyscall {
|
||||||
_, exists := f.functions[funcName]
|
_, exists := f.Functions[funcName]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position)
|
return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position)
|
||||||
@ -52,6 +52,10 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, register := range registers {
|
for _, register := range registers {
|
||||||
|
if register == f.cpu.Output[0] && root.Parent != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
f.Scope().Free(register)
|
f.Scope().Free(register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,10 @@ func (f *Function) useVariable(variable *Variable) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Comments {
|
||||||
|
f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i)
|
||||||
|
}
|
||||||
|
|
||||||
local.Alive--
|
local.Alive--
|
||||||
|
|
||||||
if local.Alive < 0 {
|
if local.Alive < 0 {
|
||||||
@ -71,7 +75,7 @@ func (f *Function) useVariable(variable *Variable) {
|
|||||||
|
|
||||||
if local.Alive == 0 {
|
if local.Alive == 0 {
|
||||||
if config.Comments {
|
if config.Comments {
|
||||||
f.Comment("%s died (%s) in scope %d", local.Name, local.Register, i)
|
f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.Free(local.Register)
|
scope.Free(local.Register)
|
||||||
@ -87,7 +91,7 @@ func (f *Function) identifierExists(name string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := f.functions[name]
|
_, exists := f.Functions[name]
|
||||||
return exists
|
return exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
"git.akyoto.dev/cli/q/src/build/ast"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
"git.akyoto.dev/cli/q/src/build/errors"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/fs"
|
"git.akyoto.dev/cli/q/src/build/fs"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
)
|
)
|
||||||
@ -15,7 +13,7 @@ type Function struct {
|
|||||||
Name string
|
Name string
|
||||||
File *fs.File
|
File *fs.File
|
||||||
Body token.List
|
Body token.List
|
||||||
state
|
State
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFunction creates a new function.
|
// NewFunction creates a new function.
|
||||||
@ -24,8 +22,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function {
|
|||||||
Name: name,
|
Name: name,
|
||||||
File: file,
|
File: file,
|
||||||
Body: body,
|
Body: body,
|
||||||
state: state{
|
State: State{
|
||||||
assembler: asm.Assembler{
|
Assembler: asm.Assembler{
|
||||||
Instructions: make([]asm.Instruction, 0, 32),
|
Instructions: make([]asm.Instruction, 0, 32),
|
||||||
},
|
},
|
||||||
cpu: cpu.CPU{
|
cpu: cpu.CPU{
|
||||||
@ -43,65 +41,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile turns a function into machine code.
|
|
||||||
func (f *Function) Compile() {
|
|
||||||
defer close(f.finished)
|
|
||||||
f.AddLabel(f.Name)
|
|
||||||
f.err = f.CompileTokens(f.Body)
|
|
||||||
f.Return()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompileTokens compiles a token list.
|
|
||||||
func (f *Function) CompileTokens(tokens token.List) error {
|
|
||||||
body, err := ast.Parse(tokens)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err.(*errors.Error).File = f.File
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.CompileAST(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompileAST compiles an abstract syntax tree.
|
|
||||||
func (f *Function) CompileAST(tree ast.AST) error {
|
|
||||||
for _, node := range tree {
|
|
||||||
err := f.CompileASTNode(node)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompileASTNode compiles a node in the AST.
|
|
||||||
func (f *Function) CompileASTNode(node ast.Node) error {
|
|
||||||
switch node := node.(type) {
|
|
||||||
case *ast.Assign:
|
|
||||||
return f.CompileAssign(node)
|
|
||||||
|
|
||||||
case *ast.Call:
|
|
||||||
return f.CompileCall(node.Expression)
|
|
||||||
|
|
||||||
case *ast.Define:
|
|
||||||
return f.CompileDefinition(node)
|
|
||||||
|
|
||||||
case *ast.Return:
|
|
||||||
return f.CompileReturn(node)
|
|
||||||
|
|
||||||
case *ast.If:
|
|
||||||
return f.CompileIf(node)
|
|
||||||
|
|
||||||
case *ast.Loop:
|
|
||||||
return f.CompileLoop(node)
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("unknown AST type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the function name.
|
// String returns the function name.
|
||||||
func (f *Function) String() string {
|
func (f *Function) String() string {
|
||||||
return f.Name
|
return f.Name
|
||||||
|
@ -9,28 +9,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (f *Function) AddLabel(label string) {
|
func (f *Function) AddLabel(label string) {
|
||||||
f.assembler.Label(asm.LABEL, label)
|
f.Assembler.Label(asm.LABEL, label)
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Call(label string) {
|
func (f *Function) Call(label string) {
|
||||||
f.assembler.Call(label)
|
f.Assembler.Call(label)
|
||||||
f.Scope().Use(f.cpu.Output[0])
|
f.Scope().Use(f.cpu.Output[0])
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Comment(format string, args ...any) {
|
func (f *Function) Comment(format string, args ...any) {
|
||||||
f.assembler.Comment(fmt.Sprintf(format, args...))
|
f.Assembler.Comment(fmt.Sprintf(format, args...))
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Jump(mnemonic asm.Mnemonic, label string) {
|
func (f *Function) Jump(mnemonic asm.Mnemonic, label string) {
|
||||||
f.assembler.Label(mnemonic, label)
|
f.Assembler.Label(mnemonic, label)
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) {
|
func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) {
|
||||||
f.assembler.Register(mnemonic, a)
|
f.Assembler.Register(mnemonic, a)
|
||||||
|
|
||||||
if mnemonic == asm.POP {
|
if mnemonic == asm.POP {
|
||||||
f.Scope().Use(a)
|
f.Scope().Use(a)
|
||||||
@ -44,7 +44,7 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int)
|
|||||||
f.SaveRegister(a)
|
f.SaveRegister(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.assembler.RegisterNumber(mnemonic, a, b)
|
f.Assembler.RegisterNumber(mnemonic, a, b)
|
||||||
|
|
||||||
if mnemonic == asm.MOVE {
|
if mnemonic == asm.MOVE {
|
||||||
f.Scope().Use(a)
|
f.Scope().Use(a)
|
||||||
@ -58,7 +58,7 @@ func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, l
|
|||||||
f.SaveRegister(register)
|
f.SaveRegister(register)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.assembler.RegisterLabel(mnemonic, register, label)
|
f.Assembler.RegisterLabel(mnemonic, register, label)
|
||||||
|
|
||||||
if mnemonic == asm.MOVE {
|
if mnemonic == asm.MOVE {
|
||||||
f.Scope().Use(register)
|
f.Scope().Use(register)
|
||||||
@ -76,7 +76,7 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu
|
|||||||
f.SaveRegister(a)
|
f.SaveRegister(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.assembler.RegisterRegister(mnemonic, a, b)
|
f.Assembler.RegisterRegister(mnemonic, a, b)
|
||||||
|
|
||||||
if mnemonic == asm.MOVE {
|
if mnemonic == asm.MOVE {
|
||||||
f.Scope().Use(a)
|
f.Scope().Use(a)
|
||||||
@ -86,12 +86,12 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Return() {
|
func (f *Function) Return() {
|
||||||
f.assembler.Return()
|
f.Assembler.Return()
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) Syscall() {
|
func (f *Function) Syscall() {
|
||||||
f.assembler.Syscall()
|
f.Assembler.Syscall()
|
||||||
f.Scope().Use(f.cpu.Output[0])
|
f.Scope().Use(f.cpu.Output[0])
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ type Scope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scope returns the current scope.
|
// Scope returns the current scope.
|
||||||
func (s *state) Scope() *Scope {
|
func (s *State) Scope() *Scope {
|
||||||
return s.scopes[len(s.scopes)-1]
|
return s.scopes[len(s.scopes)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,14 +9,14 @@ import (
|
|||||||
"git.akyoto.dev/go/color/ansi"
|
"git.akyoto.dev/go/color/ansi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// state is the data structure we embed in each function to preserve compilation state.
|
// State is the data structure we embed in each function to preserve compilation State.
|
||||||
type state struct {
|
type State struct {
|
||||||
err error
|
Assembler asm.Assembler
|
||||||
|
Functions map[string]*Function
|
||||||
|
Err error
|
||||||
scopes []*Scope
|
scopes []*Scope
|
||||||
functions map[string]*Function
|
|
||||||
registerHistory []uint64
|
registerHistory []uint64
|
||||||
finished chan struct{}
|
finished chan struct{}
|
||||||
assembler asm.Assembler
|
|
||||||
cpu cpu.CPU
|
cpu cpu.CPU
|
||||||
count counter
|
count counter
|
||||||
}
|
}
|
||||||
@ -30,10 +30,10 @@ type counter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrintInstructions shows the assembly instructions.
|
// PrintInstructions shows the assembly instructions.
|
||||||
func (s *state) PrintInstructions() {
|
func (s *State) PrintInstructions() {
|
||||||
ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
|
ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
|
||||||
|
|
||||||
for i, x := range s.assembler.Instructions {
|
for i, x := range s.Assembler.Instructions {
|
||||||
ansi.Dim.Print("│ ")
|
ansi.Dim.Print("│ ")
|
||||||
|
|
||||||
switch x.Mnemonic {
|
switch x.Mnemonic {
|
@ -41,7 +41,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
|
|||||||
f.count.data++
|
f.count.data++
|
||||||
label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data)
|
label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data)
|
||||||
value := t.Bytes[1 : len(t.Bytes)-1]
|
value := t.Bytes[1 : len(t.Bytes)-1]
|
||||||
f.assembler.SetData(label, value)
|
f.Assembler.SetData(label, value)
|
||||||
f.RegisterLabel(asm.MOVE, register, label)
|
f.RegisterLabel(asm.MOVE, register, label)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
@ -13,12 +13,12 @@ type Variable struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Variable returns the variable with the given name or `nil` if it doesn't exist.
|
// Variable returns the variable with the given name or `nil` if it doesn't exist.
|
||||||
func (s *state) Variable(name string) *Variable {
|
func (s *State) Variable(name string) *Variable {
|
||||||
return s.Scope().variables[name]
|
return s.Scope().variables[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register.
|
// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register.
|
||||||
func (s *state) VariableInRegister(register cpu.Register) *Variable {
|
func (s *State) VariableInRegister(register cpu.Register) *Variable {
|
||||||
for _, v := range s.Scope().variables {
|
for _, v := range s.Scope().variables {
|
||||||
if v.Register == register {
|
if v.Register == register {
|
||||||
return v
|
return v
|
||||||
|
@ -3,6 +3,9 @@ package tests_test
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build"
|
||||||
|
"git.akyoto.dev/go/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var examples = []struct {
|
var examples = []struct {
|
||||||
@ -22,3 +25,16 @@ func TestExamples(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkExamples(b *testing.B) {
|
||||||
|
for _, test := range examples {
|
||||||
|
b.Run(test.Name, func(b *testing.B) {
|
||||||
|
compiler := build.New(filepath.Join("..", "examples", test.Name))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := compiler.Run()
|
||||||
|
assert.Nil(b, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user