diff --git a/src/build/arch/x64/Negate.go b/src/build/arch/x64/Negate.go
new file mode 100644
index 0000000..00ae530
--- /dev/null
+++ b/src/build/arch/x64/Negate.go
@@ -0,0 +1,8 @@
+package x64
+
+import "git.akyoto.dev/cli/q/src/build/cpu"
+
+// NegateRegister negates the value in the register.
+func NegateRegister(code []byte, register cpu.Register) []byte {
+ return encode(code, AddressDirect, 0b011, register, 8, 0xF7)
+}
diff --git a/src/build/arch/x64/Negate_test.go b/src/build/arch/x64/Negate_test.go
new file mode 100644
index 0000000..dd64518
--- /dev/null
+++ b/src/build/arch/x64/Negate_test.go
@@ -0,0 +1,39 @@
+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 TestNegateRegister(t *testing.T) {
+ usagePatterns := []struct {
+ Register cpu.Register
+ Code []byte
+ }{
+ {x64.RAX, []byte{0x48, 0xF7, 0xD8}},
+ {x64.RCX, []byte{0x48, 0xF7, 0xD9}},
+ {x64.RDX, []byte{0x48, 0xF7, 0xDA}},
+ {x64.RBX, []byte{0x48, 0xF7, 0xDB}},
+ {x64.RSP, []byte{0x48, 0xF7, 0xDC}},
+ {x64.RBP, []byte{0x48, 0xF7, 0xDD}},
+ {x64.RSI, []byte{0x48, 0xF7, 0xDE}},
+ {x64.RDI, []byte{0x48, 0xF7, 0xDF}},
+ {x64.R8, []byte{0x49, 0xF7, 0xD8}},
+ {x64.R9, []byte{0x49, 0xF7, 0xD9}},
+ {x64.R10, []byte{0x49, 0xF7, 0xDA}},
+ {x64.R11, []byte{0x49, 0xF7, 0xDB}},
+ {x64.R12, []byte{0x49, 0xF7, 0xDC}},
+ {x64.R13, []byte{0x49, 0xF7, 0xDD}},
+ {x64.R14, []byte{0x49, 0xF7, 0xDE}},
+ {x64.R15, []byte{0x49, 0xF7, 0xDF}},
+ }
+
+ for _, pattern := range usagePatterns {
+ t.Logf("neg %s", pattern.Register)
+ code := x64.NegateRegister(nil, pattern.Register)
+ assert.DeepEqual(t, code, pattern.Code)
+ }
+}
diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go
index 5c3397d..e38a83f 100644
--- a/src/build/asm/Finalize.go
+++ b/src/build/asm/Finalize.go
@@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) {
})
}
+ case NEGATE:
+ switch operands := x.Data.(type) {
+ case *Register:
+ code = x64.NegateRegister(code, operands.Register)
+ }
+
case OR:
switch operands := x.Data.(type) {
case *RegisterNumber:
diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go
index 78070f8..e8c778b 100644
--- a/src/build/asm/Mnemonic.go
+++ b/src/build/asm/Mnemonic.go
@@ -4,12 +4,31 @@ type Mnemonic uint8
const (
NONE Mnemonic = iota
+
+ // Arithmetic
ADD
- AND
- CALL
- COMMENT
- COMPARE
DIV
+ MODULO
+ MUL
+ NEGATE
+ SUB
+
+ // Bitwise operations
+ AND
+ OR
+ SHIFTL
+ SHIFTRS
+ XOR
+
+ // Data movement
+ MOVE
+ LOAD
+ POP
+ PUSH
+ STORE
+
+ // Control flow
+ CALL
JE
JNE
JG
@@ -17,21 +36,13 @@ const (
JL
JLE
JUMP
- MUL
LABEL
- LOAD
- MODULO
- MOVE
- OR
- POP
- PUSH
RETURN
- SHIFTL
- SHIFTRS
- STORE
- SUB
SYSCALL
- XOR
+
+ // Others
+ COMMENT
+ COMPARE
)
// String returns a human readable version.
@@ -73,6 +84,8 @@ func (m Mnemonic) String() string {
return "move"
case MUL:
return "mul"
+ case NEGATE:
+ return "negate"
case OR:
return "or"
case POP:
diff --git a/src/build/core/ExecuteRegister.go b/src/build/core/ExecuteRegister.go
new file mode 100644
index 0000000..6627acf
--- /dev/null
+++ b/src/build/core/ExecuteRegister.go
@@ -0,0 +1,21 @@
+package core
+
+import (
+ "git.akyoto.dev/cli/q/src/build/asm"
+ "git.akyoto.dev/cli/q/src/build/cpu"
+ "git.akyoto.dev/cli/q/src/build/errors"
+ "git.akyoto.dev/cli/q/src/build/token"
+)
+
+// ExecuteRegister performs an operation on a single register.
+func (f *Function) ExecuteRegister(operation token.Token, register cpu.Register) error {
+ switch operation.Kind {
+ case token.Negate:
+ f.Register(asm.NEGATE, register)
+
+ default:
+ return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position)
+ }
+
+ return nil
+}
diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go
index 212b7ed..a0ab61f 100644
--- a/src/build/core/ExpressionToRegister.go
+++ b/src/build/core/ExpressionToRegister.go
@@ -1,6 +1,8 @@
package core
import (
+ "fmt"
+
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/cpu"
@@ -24,8 +26,19 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
return err
}
- if len(node.Children) < 2 {
- return errors.New(errors.MissingOperand, f.File, node.Token.End())
+ if len(node.Children) == 1 {
+ if !node.Token.IsUnaryOperator() {
+ fmt.Println(node.String(f.File.Bytes), node.Token.Kind)
+ return errors.New(errors.MissingOperand, f.File, node.Token.End())
+ }
+
+ err := f.ExpressionToRegister(node.Children[0], register)
+
+ if err != nil {
+ return err
+ }
+
+ return f.ExecuteRegister(node.Token, register)
}
left := node.Children[0]
diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go
index 871e49f..44f0132 100644
--- a/src/build/token/Kind.go
+++ b/src/build/token/Kind.go
@@ -38,8 +38,6 @@ const (
Shr // >>
LogicalAnd // &&
LogicalOr // ||
- Not // ! (unary)
- Negate // - (unary)
Equal // ==
Less // <
Greater // >
@@ -51,6 +49,10 @@ const (
Call // x()
Array // [x]
Separator // ,
+ _unary //
+ Not // ! (unary)
+ Negate // - (unary)
+ _unaryEnd //
_assignments //
Assign // =
AddAssign // +=
diff --git a/src/build/token/Token.go b/src/build/token/Token.go
index 99cbe97..caac815 100644
--- a/src/build/token/Token.go
+++ b/src/build/token/Token.go
@@ -43,6 +43,11 @@ func (t Token) IsOperator() bool {
return t.Kind > _operators && t.Kind < _operatorsEnd
}
+// IsUnaryOperator returns true if the token is a unary operator.
+func (t Token) IsUnaryOperator() bool {
+ return t.Kind > _unary && t.Kind < _unaryEnd
+}
+
// Reset resets the token to default values.
func (t *Token) Reset() {
t.Position = 0
diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go
index 52b3122..9e0f4fa 100644
--- a/src/build/token/Tokenize.go
+++ b/src/build/token/Tokenize.go
@@ -27,7 +27,7 @@ func Tokenize(buffer []byte) List {
case '\n':
tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1})
case '-':
- if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() {
+ if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() {
tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1})
} else {
if i+1 < Position(len(buffer)) && buffer[i+1] == '=' {
diff --git a/tests/programs/negation.q b/tests/programs/negation.q
new file mode 100644
index 0000000..aea1133
--- /dev/null
+++ b/tests/programs/negation.q
@@ -0,0 +1,7 @@
+main() {
+ syscall(60, f(-32))
+}
+
+f(x) {
+ return -x
+}
\ No newline at end of file
diff --git a/tests/programs_test.go b/tests/programs_test.go
index 82a9d42..204f314 100644
--- a/tests/programs_test.go
+++ b/tests/programs_test.go
@@ -43,6 +43,7 @@ var programs = []struct {
{"loop-lifetime", "", "", 0},
{"assert", "", "", 1},
{"negative", "", "", 32},
+ {"negation", "", "", 32},
}
func TestPrograms(t *testing.T) {