Implemented if statements
This commit is contained in:
parent
6fc234c700
commit
91e300e49a
@ -1,5 +1,11 @@
|
||||
main() {
|
||||
exit(f(1) + f(2) + f(3))
|
||||
x := f(1) + f(2) + f(3)
|
||||
|
||||
if x != 9 {
|
||||
exit(42)
|
||||
}
|
||||
|
||||
exit(0)
|
||||
}
|
||||
|
||||
exit(code) {
|
||||
|
13
src/build/arch/x64/Compare.go
Normal file
13
src/build/arch/x64/Compare.go
Normal file
@ -0,0 +1,13 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// Compares the register with the number and sets the status flags in the EFLAGS register.
|
||||
func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte {
|
||||
return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register.
|
||||
func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte {
|
||||
return regReg(code, byte(registerB), byte(registerA), 0x39)
|
||||
}
|
88
src/build/arch/x64/Compare_test.go
Normal file
88
src/build/arch/x64/Compare_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestCompareRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}},
|
||||
{x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}},
|
||||
{x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}},
|
||||
{x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}},
|
||||
{x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}},
|
||||
{x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}},
|
||||
{x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}},
|
||||
{x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}},
|
||||
{x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}},
|
||||
{x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}},
|
||||
{x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}},
|
||||
{x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}},
|
||||
{x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}},
|
||||
{x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}},
|
||||
{x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}},
|
||||
{x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}},
|
||||
|
||||
{x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("cmp %s, %x", pattern.Register, pattern.Number)
|
||||
code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}},
|
||||
{x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}},
|
||||
{x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}},
|
||||
{x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}},
|
||||
{x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}},
|
||||
{x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}},
|
||||
{x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}},
|
||||
{x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}},
|
||||
{x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}},
|
||||
{x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}},
|
||||
{x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}},
|
||||
{x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}},
|
||||
{x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}},
|
||||
{x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}},
|
||||
{x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}},
|
||||
{x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("cmp %s, %s", pattern.Left, pattern.Right)
|
||||
code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -3,9 +3,43 @@ package x64
|
||||
// Jump continues program flow at the new address.
|
||||
// The address is relative to the next instruction.
|
||||
func Jump8(code []byte, address int8) []byte {
|
||||
return jump8(code, 0xEB, address)
|
||||
}
|
||||
|
||||
// JumpIfLess jumps if the result was less.
|
||||
func Jump8IfLess(code []byte, address int8) []byte {
|
||||
return jump8(code, 0x7C, address)
|
||||
}
|
||||
|
||||
// JumpIfLessOrEqual jumps if the result was less or equal.
|
||||
func Jump8IfLessOrEqual(code []byte, address int8) []byte {
|
||||
return jump8(code, 0x7E, address)
|
||||
}
|
||||
|
||||
// JumpIfGreater jumps if the result was greater.
|
||||
func Jump8IfGreater(code []byte, address int8) []byte {
|
||||
return jump8(code, 0x7F, address)
|
||||
}
|
||||
|
||||
// JumpIfGreaterOrEqual jumps if the result was greater or equal.
|
||||
func Jump8IfGreaterOrEqual(code []byte, address int8) []byte {
|
||||
return jump8(code, 0x7D, address)
|
||||
}
|
||||
|
||||
// JumpIfEqual jumps if the result was equal.
|
||||
func Jump8IfEqual(code []byte, address int8) []byte {
|
||||
return jump8(code, 0x74, address)
|
||||
}
|
||||
|
||||
// JumpIfNotEqual jumps if the result was not equal.
|
||||
func Jump8IfNotEqual(code []byte, address int8) []byte {
|
||||
return jump8(code, 0x75, address)
|
||||
}
|
||||
|
||||
func jump8(code []byte, opCode byte, address int8) []byte {
|
||||
return append(
|
||||
code,
|
||||
0xEB,
|
||||
opCode,
|
||||
byte(address),
|
||||
)
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
CallRegisters = SyscallRegisters
|
||||
GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15}
|
||||
SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9}
|
||||
ReturnValueRegisters = []cpu.Register{RAX, RCX, R11}
|
||||
GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15}
|
||||
CallRegisters = SyscallRegisters
|
||||
ReturnValueRegisters = SyscallRegisters
|
||||
)
|
||||
|
@ -66,8 +66,32 @@ func (a Assembler) Finalize() ([]byte, []byte) {
|
||||
case COMMENT:
|
||||
continue
|
||||
|
||||
case COMPARE:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.CompareRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case JUMP, JE, JNE, JG, JL, JGE, JLE:
|
||||
switch x.Mnemonic {
|
||||
case JUMP:
|
||||
code = x64.Jump8(code, 0x00)
|
||||
case JE:
|
||||
code = x64.Jump8IfEqual(code, 0x00)
|
||||
case JNE:
|
||||
code = x64.Jump8IfNotEqual(code, 0x00)
|
||||
case JG:
|
||||
code = x64.Jump8IfGreater(code, 0x00)
|
||||
case JL:
|
||||
code = x64.Jump8IfLess(code, 0x00)
|
||||
case JGE:
|
||||
code = x64.Jump8IfGreaterOrEqual(code, 0x00)
|
||||
case JLE:
|
||||
code = x64.Jump8IfLessOrEqual(code, 0x00)
|
||||
}
|
||||
|
||||
size := 1
|
||||
label := x.Data.(*Label)
|
||||
nextInstructionAddress := Address(len(code))
|
||||
|
@ -34,10 +34,10 @@ func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) {
|
||||
})
|
||||
}
|
||||
|
||||
// Label adds a label at the current position.
|
||||
func (a *Assembler) Label(name string) {
|
||||
// Label adds an instruction using a label.
|
||||
func (a *Assembler) Label(mnemonic Mnemonic, name string) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: LABEL,
|
||||
Mnemonic: mnemonic,
|
||||
Data: &Label{
|
||||
Name: name,
|
||||
},
|
||||
|
@ -7,7 +7,14 @@ const (
|
||||
ADD
|
||||
CALL
|
||||
COMMENT
|
||||
COMPARE
|
||||
DIV
|
||||
JE
|
||||
JNE
|
||||
JG
|
||||
JGE
|
||||
JL
|
||||
JLE
|
||||
JUMP
|
||||
MUL
|
||||
LABEL
|
||||
@ -28,10 +35,24 @@ func (m Mnemonic) String() string {
|
||||
return "call"
|
||||
case COMMENT:
|
||||
return "comment"
|
||||
case COMPARE:
|
||||
return "compare"
|
||||
case DIV:
|
||||
return "div"
|
||||
case JUMP:
|
||||
return "jump"
|
||||
case JE:
|
||||
return "jump =="
|
||||
case JNE:
|
||||
return "jump !="
|
||||
case JL:
|
||||
return "jump <"
|
||||
case JG:
|
||||
return "jump >"
|
||||
case JLE:
|
||||
return "jump <="
|
||||
case JGE:
|
||||
return "jump >="
|
||||
case LABEL:
|
||||
return "label"
|
||||
case MOVE:
|
||||
|
@ -37,6 +37,15 @@ func (node *Define) String() string {
|
||||
return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value)
|
||||
}
|
||||
|
||||
type If struct {
|
||||
Condition *expression.Expression
|
||||
Body AST
|
||||
}
|
||||
|
||||
func (node *If) String() string {
|
||||
return fmt.Sprintf("(if %s %s)", node.Condition, node.Body)
|
||||
}
|
||||
|
||||
type Loop struct {
|
||||
Body AST
|
||||
}
|
||||
|
@ -47,6 +47,22 @@ func toASTNode(tokens token.List) (Node, error) {
|
||||
tree, err := Parse(tokens[blockStart:blockEnd])
|
||||
return &Loop{Body: tree}, err
|
||||
|
||||
case keyword.If:
|
||||
blockStart := tokens.IndexKind(token.BlockStart) + 1
|
||||
blockEnd := tokens.LastIndexKind(token.BlockEnd)
|
||||
|
||||
if blockStart == -1 {
|
||||
return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End())
|
||||
}
|
||||
|
||||
if blockEnd == -1 {
|
||||
return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
|
||||
}
|
||||
|
||||
condition := expression.Parse(tokens[1:token.BlockStart])
|
||||
tree, err := Parse(tokens[blockStart:blockEnd])
|
||||
return &If{Condition: condition, Body: tree}, err
|
||||
|
||||
default:
|
||||
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position)
|
||||
}
|
||||
|
48
src/build/core/CompileIf.go
Normal file
48
src/build/core/CompileIf.go
Normal file
@ -0,0 +1,48 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileIf compiles a branch instruction.
|
||||
func (f *Function) CompileIf(branch *ast.If) error {
|
||||
condition := branch.Condition
|
||||
tmpRight := f.cpu.Input[1]
|
||||
err := f.ExpressionToRegister(condition.Children[1], tmpRight)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpLeft := f.cpu.Input[0]
|
||||
err = f.ExpressionToRegister(condition.Children[0], tmpLeft)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight)
|
||||
elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch)
|
||||
|
||||
switch condition.Token.Text() {
|
||||
case "==":
|
||||
f.assembler.Label(asm.JNE, elseLabel)
|
||||
case "!=":
|
||||
f.assembler.Label(asm.JE, elseLabel)
|
||||
case ">":
|
||||
f.assembler.Label(asm.JLE, elseLabel)
|
||||
case "<":
|
||||
f.assembler.Label(asm.JGE, elseLabel)
|
||||
case ">=":
|
||||
f.assembler.Label(asm.JL, elseLabel)
|
||||
case "<=":
|
||||
f.assembler.Label(asm.JG, elseLabel)
|
||||
}
|
||||
|
||||
defer f.assembler.Label(asm.LABEL, elseLabel)
|
||||
f.count.branch++
|
||||
return f.CompileAST(branch.Body)
|
||||
}
|
@ -3,13 +3,14 @@ package core
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileLoop compiles a loop instruction.
|
||||
func (f *Function) CompileLoop(loop *ast.Loop) error {
|
||||
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
||||
f.assembler.Label(label)
|
||||
f.assembler.Label(asm.LABEL, label)
|
||||
defer f.assembler.Jump(label)
|
||||
f.count.loop++
|
||||
return f.CompileAST(loop.Body)
|
||||
|
@ -45,7 +45,7 @@ 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.assembler.Label(f.Name)
|
||||
f.assembler.Label(asm.LABEL, f.Name)
|
||||
f.err = f.CompileTokens(f.Body)
|
||||
f.assembler.Return()
|
||||
}
|
||||
@ -90,6 +90,9 @@ func (f *Function) CompileASTNode(node ast.Node) error {
|
||||
case *ast.Return:
|
||||
return f.CompileReturn(node)
|
||||
|
||||
case *ast.If:
|
||||
return f.CompileIf(node)
|
||||
|
||||
case *ast.Loop:
|
||||
return f.CompileLoop(node)
|
||||
|
||||
|
@ -22,6 +22,7 @@ type state struct {
|
||||
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||
type counter struct {
|
||||
loop int
|
||||
branch int
|
||||
}
|
||||
|
||||
// PrintInstructions shows the assembly instructions.
|
||||
|
@ -1,12 +1,14 @@
|
||||
package keyword
|
||||
|
||||
const (
|
||||
If = "if"
|
||||
Loop = "loop"
|
||||
Return = "return"
|
||||
)
|
||||
|
||||
// Map is a map of all keywords used in the language.
|
||||
var Map = map[string][]byte{
|
||||
If: []byte(If),
|
||||
Loop: []byte(Loop),
|
||||
Return: []byte(Return),
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ var examples = []struct {
|
||||
ExpectedOutput string
|
||||
ExpectedExitCode int
|
||||
}{
|
||||
{"hello", "", 9},
|
||||
{"hello", "", 0},
|
||||
{"write", "ELF", 0},
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user