Simplified file structure
This commit is contained in:
13
src/core/AddBytes.go
Normal file
13
src/core/AddBytes.go
Normal file
@ -0,0 +1,13 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AddBytes adds a sequence of bytes and returns its address as a label.
|
||||
func (f *Function) AddBytes(value []byte) string {
|
||||
f.count.data++
|
||||
label := fmt.Sprintf("data_%s_%d", f.UniqueName, f.count.data)
|
||||
f.Assembler.SetData(label, value)
|
||||
return label
|
||||
}
|
46
src/core/Compare.go
Normal file
46
src/core/Compare.go
Normal file
@ -0,0 +1,46 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Compare evaluates a boolean expression.
|
||||
func (f *Function) Compare(comparison *expression.Expression) error {
|
||||
left := comparison.Children[0]
|
||||
right := comparison.Children[1]
|
||||
|
||||
if left.IsLeaf() && left.Token.Kind == token.Identifier {
|
||||
name := left.Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
return f.Execute(comparison.Token, variable.Register, right)
|
||||
}
|
||||
|
||||
if ast.IsFunctionCall(left) && right.IsLeaf() {
|
||||
_, err := f.CompileCall(left)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.ExecuteLeaf(comparison.Token, f.CPU.Output[0], right.Token)
|
||||
}
|
||||
|
||||
tmp := f.NewRegister()
|
||||
_, err := f.ExpressionToRegister(left, tmp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.FreeRegister(tmp)
|
||||
return f.Execute(comparison.Token, tmp, right)
|
||||
}
|
12
src/core/Compile.go
Normal file
12
src/core/Compile.go
Normal file
@ -0,0 +1,12 @@
|
||||
package core
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {
|
||||
f.AddLabel(f.UniqueName)
|
||||
f.Err = f.CompileTokens(f.Body)
|
||||
f.Return()
|
||||
|
||||
for _, call := range f.deferred {
|
||||
call()
|
||||
}
|
||||
}
|
18
src/core/CompileAST.go
Normal file
18
src/core/CompileAST.go
Normal file
@ -0,0 +1,18 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
51
src/core/CompileASTNode.go
Normal file
51
src/core/CompileASTNode.go
Normal file
@ -0,0 +1,51 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
)
|
||||
|
||||
// CompileASTNode compiles a node in the AST.
|
||||
func (f *Function) CompileASTNode(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Assert:
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileAssert(node)
|
||||
|
||||
case *ast.Assign:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileAssign(node)
|
||||
|
||||
case *ast.Call:
|
||||
f.Fold(node.Expression)
|
||||
_, err := f.CompileCall(node.Expression)
|
||||
return err
|
||||
|
||||
case *ast.Define:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileDefinition(node)
|
||||
|
||||
case *ast.If:
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileIf(node)
|
||||
|
||||
case *ast.Loop:
|
||||
return f.CompileLoop(node)
|
||||
|
||||
case *ast.Return:
|
||||
for _, value := range node.Values {
|
||||
f.Fold(value)
|
||||
}
|
||||
|
||||
return f.CompileReturn(node)
|
||||
|
||||
case *ast.Switch:
|
||||
for _, c := range node.Cases {
|
||||
f.Fold(c.Condition)
|
||||
}
|
||||
|
||||
return f.CompileSwitch(node)
|
||||
|
||||
default:
|
||||
panic("unknown AST type")
|
||||
}
|
||||
}
|
29
src/core/CompileAssert.go
Normal file
29
src/core/CompileAssert.go
Normal file
@ -0,0 +1,29 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
)
|
||||
|
||||
// CompileAssert compiles an assertion.
|
||||
func (f *Function) CompileAssert(assert *ast.Assert) error {
|
||||
f.count.assert++
|
||||
success := fmt.Sprintf("%s_assert_%d_true", f.UniqueName, f.count.assert)
|
||||
fail := fmt.Sprintf("%s_assert_%d_false", f.UniqueName, f.count.assert)
|
||||
err := f.CompileCondition(assert.Condition, success, fail)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.AddLabel(success)
|
||||
|
||||
f.Defer(func() {
|
||||
f.AddLabel(fail)
|
||||
f.Jump(asm.JUMP, "_crash")
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
36
src/core/CompileAssign.go
Normal file
36
src/core/CompileAssign.go
Normal file
@ -0,0 +1,36 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// CompileAssign compiles an assign statement.
|
||||
func (f *Function) CompileAssign(node *ast.Assign) error {
|
||||
operator := node.Expression.Token
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
if left.IsLeaf() {
|
||||
name := left.Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
return f.Execute(operator, variable.Register, right)
|
||||
}
|
||||
|
||||
if left.Token.Kind == token.Array {
|
||||
return f.CompileAssignArray(node)
|
||||
}
|
||||
|
||||
if left.Token.Kind == token.Separator && right.Token.Kind == token.Div {
|
||||
return f.CompileAssignDivision(node)
|
||||
}
|
||||
|
||||
return errors.New(errors.NotImplemented, f.File, left.Token.Position)
|
||||
}
|
38
src/core/CompileAssignArray.go
Normal file
38
src/core/CompileAssignArray.go
Normal file
@ -0,0 +1,38 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
)
|
||||
|
||||
// CompileAssignArray compiles an assign statement for array elements.
|
||||
func (f *Function) CompileAssignArray(node *ast.Assign) error {
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
name := left.Children[0].Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
|
||||
index := left.Children[1]
|
||||
offset, err := f.Number(index.Token)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
memory := asm.Memory{
|
||||
Base: variable.Register,
|
||||
Offset: byte(offset),
|
||||
Length: byte(1),
|
||||
}
|
||||
|
||||
_, err = f.ExpressionToMemory(right, memory)
|
||||
return err
|
||||
}
|
43
src/core/CompileAssignDivision.go
Normal file
43
src/core/CompileAssignDivision.go
Normal file
@ -0,0 +1,43 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
)
|
||||
|
||||
// CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right.
|
||||
func (f *Function) CompileAssignDivision(node *ast.Assign) error {
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
quotient := left.Children[0]
|
||||
name := quotient.Token.Text(f.File.Bytes)
|
||||
quotientVariable := f.VariableByName(name)
|
||||
|
||||
if quotientVariable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position)
|
||||
}
|
||||
|
||||
remainder := left.Children[1]
|
||||
name = remainder.Token.Text(f.File.Bytes)
|
||||
remainderVariable := f.VariableByName(name)
|
||||
|
||||
if remainderVariable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position)
|
||||
}
|
||||
|
||||
dividend := right.Children[0]
|
||||
_, dividendRegister, err := f.Evaluate(dividend)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
divisor := right.Children[1]
|
||||
err = f.Execute(right.Token, dividendRegister, divisor)
|
||||
f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX)
|
||||
f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX)
|
||||
return err
|
||||
}
|
109
src/core/CompileCall.go
Normal file
109
src/core/CompileCall.go
Normal file
@ -0,0 +1,109 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
)
|
||||
|
||||
// CompileCall executes a function call.
|
||||
// All call registers must hold the correct parameter values before the function invocation.
|
||||
// Registers that are in use must be saved if they are modified by the function.
|
||||
// After the function call, they must be restored in reverse order.
|
||||
func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
|
||||
var (
|
||||
pkg = f.Package
|
||||
nameNode = root.Children[0]
|
||||
fn *Function
|
||||
name string
|
||||
fullName string
|
||||
exists bool
|
||||
)
|
||||
|
||||
if nameNode.IsLeaf() {
|
||||
name = nameNode.Token.Text(f.File.Bytes)
|
||||
|
||||
if name == "syscall" {
|
||||
return nil, f.CompileSyscall(root)
|
||||
}
|
||||
} else {
|
||||
pkg = nameNode.Children[0].Token.Text(f.File.Bytes)
|
||||
name = nameNode.Children[1].Token.Text(f.File.Bytes)
|
||||
}
|
||||
|
||||
if pkg != f.File.Package {
|
||||
if f.File.Imports == nil {
|
||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
imp, exists := f.File.Imports[pkg]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
imp.Used = true
|
||||
}
|
||||
|
||||
tmp := strings.Builder{}
|
||||
tmp.WriteString(pkg)
|
||||
tmp.WriteString(".")
|
||||
tmp.WriteString(name)
|
||||
fullName = tmp.String()
|
||||
fn, exists = f.Functions[fullName]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
parameters := root.Children[1:]
|
||||
registers := f.CPU.Input[:len(parameters)]
|
||||
|
||||
for i := len(parameters) - 1; i >= 0; i-- {
|
||||
typ, err := f.ExpressionToRegister(parameters[i], registers[i])
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if typ != fn.Parameters[i].Type {
|
||||
return nil, errors.New(&errors.TypeMismatch{
|
||||
Encountered: string(typ),
|
||||
Expected: string(fn.Parameters[i].Type),
|
||||
ParameterName: fn.Parameters[i].Name,
|
||||
}, f.File, parameters[i].Token.Position)
|
||||
}
|
||||
}
|
||||
|
||||
for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
for _, register := range f.CPU.General {
|
||||
if f.RegisterIsUsed(register) {
|
||||
f.Register(asm.PUSH, register)
|
||||
}
|
||||
}
|
||||
|
||||
f.Call(fullName)
|
||||
|
||||
for _, register := range registers {
|
||||
if register == f.CPU.Output[0] && root.Parent != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
f.FreeRegister(register)
|
||||
}
|
||||
|
||||
for i := len(f.CPU.General) - 1; i >= 0; i-- {
|
||||
register := f.CPU.General[i]
|
||||
|
||||
if f.RegisterIsUsed(register) {
|
||||
f.Register(asm.POP, register)
|
||||
}
|
||||
}
|
||||
|
||||
return fn, nil
|
||||
}
|
76
src/core/CompileCondition.go
Normal file
76
src/core/CompileCondition.go
Normal file
@ -0,0 +1,76 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition.
|
||||
func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error {
|
||||
switch condition.Token.Kind {
|
||||
case token.LogicalOr:
|
||||
f.count.subBranch++
|
||||
leftFailLabel := fmt.Sprintf("%s_false_%d", f.UniqueName, f.count.subBranch)
|
||||
|
||||
// Left
|
||||
left := condition.Children[0]
|
||||
err := f.CompileCondition(left, successLabel, leftFailLabel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.JumpIfTrue(left.Token.Kind, successLabel)
|
||||
|
||||
// Right
|
||||
f.AddLabel(leftFailLabel)
|
||||
right := condition.Children[1]
|
||||
err = f.CompileCondition(right, successLabel, failLabel)
|
||||
|
||||
if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() {
|
||||
f.JumpIfTrue(right.Token.Kind, successLabel)
|
||||
} else {
|
||||
f.JumpIfFalse(right.Token.Kind, failLabel)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
case token.LogicalAnd:
|
||||
f.count.subBranch++
|
||||
leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.UniqueName, f.count.subBranch)
|
||||
|
||||
// Left
|
||||
left := condition.Children[0]
|
||||
err := f.CompileCondition(left, leftSuccessLabel, failLabel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.JumpIfFalse(left.Token.Kind, failLabel)
|
||||
|
||||
// Right
|
||||
f.AddLabel(leftSuccessLabel)
|
||||
right := condition.Children[1]
|
||||
err = f.CompileCondition(right, successLabel, failLabel)
|
||||
|
||||
if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() {
|
||||
f.JumpIfTrue(right.Token.Kind, successLabel)
|
||||
} else {
|
||||
f.JumpIfFalse(right.Token.Kind, failLabel)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
default:
|
||||
err := f.Compare(condition)
|
||||
|
||||
if condition.Parent == nil {
|
||||
f.JumpIfFalse(condition.Token.Kind, failLabel)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
66
src/core/CompileDefinition.go
Normal file
66
src/core/CompileDefinition.go
Normal file
@ -0,0 +1,66 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// CompileDefinition compiles a variable definition.
|
||||
func (f *Function) CompileDefinition(node *ast.Define) error {
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
if left.IsLeaf() {
|
||||
variable, err := f.Define(left)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
typ, err := f.ExpressionToRegister(right, variable.Register)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
variable.Type = typ
|
||||
|
||||
if variable.Type == types.Invalid {
|
||||
return errors.New(errors.UnknownType, f.File, node.Expression.Token.End())
|
||||
}
|
||||
|
||||
f.AddVariable(variable)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ast.IsFunctionCall(right) {
|
||||
return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position)
|
||||
}
|
||||
|
||||
count := 0
|
||||
called, err := f.CompileCall(right)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return left.EachLeaf(func(leaf *expression.Expression) error {
|
||||
variable, err := f.Define(leaf)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if called != nil {
|
||||
variable.Type = called.ReturnTypes[count]
|
||||
}
|
||||
|
||||
f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count])
|
||||
f.AddVariable(variable)
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
}
|
55
src/core/CompileIf.go
Normal file
55
src/core/CompileIf.go
Normal file
@ -0,0 +1,55 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
)
|
||||
|
||||
// CompileIf compiles a branch instruction.
|
||||
func (f *Function) CompileIf(branch *ast.If) error {
|
||||
f.count.branch++
|
||||
|
||||
var (
|
||||
end string
|
||||
success = fmt.Sprintf("%s_if_%d_true", f.UniqueName, f.count.branch)
|
||||
fail = fmt.Sprintf("%s_if_%d_false", f.UniqueName, f.count.branch)
|
||||
err = f.CompileCondition(branch.Condition, success, fail)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.AddLabel(success)
|
||||
f.PushScope(branch.Body, f.File.Bytes)
|
||||
err = f.CompileAST(branch.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if branch.Else != nil {
|
||||
end = fmt.Sprintf("%s_if_%d_end", f.UniqueName, f.count.branch)
|
||||
f.Jump(asm.JUMP, end)
|
||||
}
|
||||
|
||||
f.PopScope()
|
||||
f.AddLabel(fail)
|
||||
|
||||
if branch.Else != nil {
|
||||
f.PushScope(branch.Else, f.File.Bytes)
|
||||
err = f.CompileAST(branch.Else)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.PopScope()
|
||||
f.AddLabel(end)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
25
src/core/CompileLoop.go
Normal file
25
src/core/CompileLoop.go
Normal file
@ -0,0 +1,25 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
)
|
||||
|
||||
// CompileLoop compiles a loop instruction.
|
||||
func (f *Function) CompileLoop(loop *ast.Loop) error {
|
||||
for _, register := range f.CPU.Input {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
f.count.loop++
|
||||
label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop)
|
||||
f.AddLabel(label)
|
||||
scope := f.PushScope(loop.Body, f.File.Bytes)
|
||||
scope.InLoop = true
|
||||
err := f.CompileAST(loop.Body)
|
||||
f.Jump(asm.JUMP, label)
|
||||
f.PopScope()
|
||||
return err
|
||||
}
|
35
src/core/CompileReturn.go
Normal file
35
src/core/CompileReturn.go
Normal file
@ -0,0 +1,35 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// CompileReturn compiles a return instruction.
|
||||
func (f *Function) CompileReturn(node *ast.Return) error {
|
||||
defer f.Return()
|
||||
|
||||
if len(node.Values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := len(node.Values) - 1; i >= 0; i-- {
|
||||
typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if typ != types.Any && typ != f.ReturnTypes[i] {
|
||||
return errors.New(&errors.TypeMismatch{
|
||||
Encountered: string(typ),
|
||||
Expected: string(f.ReturnTypes[i]),
|
||||
ParameterName: "",
|
||||
IsReturn: true,
|
||||
}, f.File, node.Values[i].Token.Position)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
55
src/core/CompileSwitch.go
Normal file
55
src/core/CompileSwitch.go
Normal file
@ -0,0 +1,55 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
)
|
||||
|
||||
// CompileSwitch compiles a multi-branch instruction.
|
||||
func (f *Function) CompileSwitch(s *ast.Switch) error {
|
||||
f.count.multiBranch++
|
||||
end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch)
|
||||
|
||||
for _, branch := range s.Cases {
|
||||
if branch.Condition == nil {
|
||||
f.PushScope(branch.Body, f.File.Bytes)
|
||||
err := f.CompileAST(branch.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.PopScope()
|
||||
break
|
||||
}
|
||||
|
||||
f.count.branch++
|
||||
|
||||
var (
|
||||
success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch)
|
||||
fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch)
|
||||
err = f.CompileCondition(branch.Condition, success, fail)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.AddLabel(success)
|
||||
f.PushScope(branch.Body, f.File.Bytes)
|
||||
err = f.CompileAST(branch.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Jump(asm.JUMP, end)
|
||||
f.PopScope()
|
||||
f.AddLabel(fail)
|
||||
}
|
||||
|
||||
f.AddLabel(end)
|
||||
return nil
|
||||
}
|
32
src/core/CompileSyscall.go
Normal file
32
src/core/CompileSyscall.go
Normal file
@ -0,0 +1,32 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
)
|
||||
|
||||
// CompileSyscall executes a kernel syscall.
|
||||
func (f *Function) CompileSyscall(root *expression.Expression) error {
|
||||
parameters := root.Children[1:]
|
||||
registers := f.CPU.SyscallInput[:len(parameters)]
|
||||
err := f.ExpressionsToRegisters(parameters, registers)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, register := range f.CPU.SyscallOutput {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
f.Syscall()
|
||||
|
||||
for _, register := range registers {
|
||||
if register == f.CPU.SyscallOutput[0] && root.Parent != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
f.FreeRegister(register)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
src/core/CompileTokens.go
Normal file
19
src/core/CompileTokens.go
Normal file
@ -0,0 +1,19 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// CompileTokens compiles a token list.
|
||||
func (f *Function) CompileTokens(tokens []token.Token) error {
|
||||
body, err := ast.Parse(tokens, f.File.Bytes)
|
||||
|
||||
if err != nil {
|
||||
err.(*errors.Error).File = f.File
|
||||
return err
|
||||
}
|
||||
|
||||
return f.CompileAST(body)
|
||||
}
|
6
src/core/Defer.go
Normal file
6
src/core/Defer.go
Normal file
@ -0,0 +1,6 @@
|
||||
package core
|
||||
|
||||
// Defer executes the callback at the end of function compilation.
|
||||
func (f *Function) Defer(call func()) {
|
||||
f.deferred = append(f.deferred, call)
|
||||
}
|
31
src/core/Define.go
Normal file
31
src/core/Define.go
Normal file
@ -0,0 +1,31 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/scope"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Define defines a new variable.
|
||||
func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) {
|
||||
name := leaf.Token.Text(f.File.Bytes)
|
||||
|
||||
if f.IdentifierExists(name) {
|
||||
return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position)
|
||||
}
|
||||
|
||||
uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1
|
||||
|
||||
if uses == 0 {
|
||||
return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position)
|
||||
}
|
||||
|
||||
variable := &scope.Variable{
|
||||
Name: name,
|
||||
Register: f.NewRegister(),
|
||||
Alive: uses,
|
||||
}
|
||||
|
||||
return variable, nil
|
||||
}
|
25
src/core/Evaluate.go
Normal file
25
src/core/Evaluate.go
Normal file
@ -0,0 +1,25 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// Evaluate evaluates an expression and returns a register that contains the value of the expression.
|
||||
func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, error) {
|
||||
if expr.Token.Kind == token.Identifier {
|
||||
name := expr.Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable.Alive == 1 {
|
||||
f.UseVariable(variable)
|
||||
return variable.Type, variable.Register, nil
|
||||
}
|
||||
}
|
||||
|
||||
tmp := f.NewRegister()
|
||||
typ, err := f.ExpressionToRegister(expr, tmp)
|
||||
return typ, tmp, err
|
||||
}
|
36
src/core/Execute.go
Normal file
36
src/core/Execute.go
Normal file
@ -0,0 +1,36 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Execute executes an operation on a register with a value operand.
|
||||
func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error {
|
||||
if value.IsLeaf() {
|
||||
return f.ExecuteLeaf(operation, register, value.Token)
|
||||
}
|
||||
|
||||
if ast.IsFunctionCall(value) {
|
||||
_, err := f.CompileCall(value)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.ExecuteRegisterRegister(operation, register, f.CPU.Output[0])
|
||||
}
|
||||
|
||||
tmp := f.NewRegister()
|
||||
defer f.FreeRegister(tmp)
|
||||
|
||||
_, err := f.ExpressionToRegister(value, tmp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.ExecuteRegisterRegister(operation, register, tmp)
|
||||
}
|
40
src/core/ExecuteLeaf.go
Normal file
40
src/core/ExecuteLeaf.go
Normal file
@ -0,0 +1,40 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// ExecuteLeaf performs an operation on a register with the given leaf operand.
|
||||
func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error {
|
||||
switch operand.Kind {
|
||||
case token.Identifier:
|
||||
name := operand.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
return f.ExecuteRegisterRegister(operation, register, variable.Register)
|
||||
|
||||
case token.Number, token.Rune:
|
||||
number, err := f.Number(operand)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.ExecuteRegisterNumber(operation, register, number)
|
||||
|
||||
case token.String:
|
||||
if operation.Kind == token.Assign {
|
||||
_, err := f.TokenToRegister(operand, register)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New(errors.NotImplemented, f.File, operation.Position)
|
||||
}
|
21
src/core/ExecuteRegister.go
Normal file
21
src/core/ExecuteRegister.go
Normal file
@ -0,0 +1,21 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/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
|
||||
}
|
69
src/core/ExecuteRegisterNumber.go
Normal file
69
src/core/ExecuteRegisterNumber.go
Normal file
@ -0,0 +1,69 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// ExecuteRegisterNumber performs an operation on a register and a number.
|
||||
func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error {
|
||||
if !operation.IsAssignment() && !operation.IsComparison() {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
switch operation.Kind {
|
||||
case token.Add, token.AddAssign:
|
||||
f.RegisterNumber(asm.ADD, register, number)
|
||||
|
||||
case token.Sub, token.SubAssign:
|
||||
f.RegisterNumber(asm.SUB, register, number)
|
||||
|
||||
case token.Mul, token.MulAssign:
|
||||
f.RegisterNumber(asm.MUL, register, number)
|
||||
|
||||
case token.Div, token.DivAssign:
|
||||
f.SaveRegister(x64.RAX)
|
||||
f.SaveRegister(x64.RDX)
|
||||
tmp := f.NewRegister()
|
||||
f.RegisterNumber(asm.MOVE, tmp, number)
|
||||
f.RegisterRegister(asm.DIV, register, tmp)
|
||||
f.FreeRegister(tmp)
|
||||
|
||||
case token.Mod, token.ModAssign:
|
||||
f.SaveRegister(x64.RAX)
|
||||
f.SaveRegister(x64.RDX)
|
||||
tmp := f.NewRegister()
|
||||
f.RegisterNumber(asm.MOVE, tmp, number)
|
||||
f.RegisterRegister(asm.MODULO, register, tmp)
|
||||
f.FreeRegister(tmp)
|
||||
|
||||
case token.And, token.AndAssign:
|
||||
f.RegisterNumber(asm.AND, register, number)
|
||||
|
||||
case token.Or, token.OrAssign:
|
||||
f.RegisterNumber(asm.OR, register, number)
|
||||
|
||||
case token.Xor, token.XorAssign:
|
||||
f.RegisterNumber(asm.XOR, register, number)
|
||||
|
||||
case token.Shl, token.ShlAssign:
|
||||
f.RegisterNumber(asm.SHIFTL, register, number)
|
||||
|
||||
case token.Shr, token.ShrAssign:
|
||||
f.RegisterNumber(asm.SHIFTRS, register, number)
|
||||
|
||||
case token.Assign:
|
||||
f.RegisterNumber(asm.MOVE, register, number)
|
||||
|
||||
case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual:
|
||||
f.RegisterNumber(asm.COMPARE, register, number)
|
||||
|
||||
default:
|
||||
return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
57
src/core/ExecuteRegisterRegister.go
Normal file
57
src/core/ExecuteRegisterRegister.go
Normal file
@ -0,0 +1,57 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// ExecuteRegisterRegister performs an operation on two registers.
|
||||
func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.Register, operand cpu.Register) error {
|
||||
if !operation.IsAssignment() && !operation.IsComparison() {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
switch operation.Kind {
|
||||
case token.Add, token.AddAssign:
|
||||
f.RegisterRegister(asm.ADD, register, operand)
|
||||
|
||||
case token.Sub, token.SubAssign:
|
||||
f.RegisterRegister(asm.SUB, register, operand)
|
||||
|
||||
case token.Mul, token.MulAssign:
|
||||
f.RegisterRegister(asm.MUL, register, operand)
|
||||
|
||||
case token.Div, token.DivAssign:
|
||||
f.SaveRegister(x64.RAX)
|
||||
f.SaveRegister(x64.RDX)
|
||||
f.RegisterRegister(asm.DIV, register, operand)
|
||||
|
||||
case token.Mod, token.ModAssign:
|
||||
f.SaveRegister(x64.RAX)
|
||||
f.SaveRegister(x64.RDX)
|
||||
f.RegisterRegister(asm.MODULO, register, operand)
|
||||
|
||||
case token.And, token.AndAssign:
|
||||
f.RegisterRegister(asm.AND, register, operand)
|
||||
|
||||
case token.Or, token.OrAssign:
|
||||
f.RegisterRegister(asm.OR, register, operand)
|
||||
|
||||
case token.Xor, token.XorAssign:
|
||||
f.RegisterRegister(asm.XOR, register, operand)
|
||||
|
||||
case token.Assign:
|
||||
f.RegisterRegister(asm.MOVE, register, operand)
|
||||
|
||||
case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual:
|
||||
f.RegisterRegister(asm.COMPARE, register, operand)
|
||||
|
||||
default:
|
||||
return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
35
src/core/ExpressionToMemory.go
Normal file
35
src/core/ExpressionToMemory.go
Normal file
@ -0,0 +1,35 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/sizeof"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// ExpressionToMemory puts the result of an expression into the specified memory address.
|
||||
func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) {
|
||||
if node.IsLeaf() && node.Token.IsNumeric() {
|
||||
number, err := f.Number(node.Token)
|
||||
|
||||
if err != nil {
|
||||
return types.Invalid, err
|
||||
}
|
||||
|
||||
size := byte(sizeof.Signed(int64(number)))
|
||||
|
||||
if size != memory.Length {
|
||||
return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position)
|
||||
}
|
||||
|
||||
f.MemoryNumber(asm.STORE, memory, number)
|
||||
return types.Int, nil
|
||||
}
|
||||
|
||||
tmp := f.NewRegister()
|
||||
defer f.FreeRegister(tmp)
|
||||
typ, err := f.ExpressionToRegister(node, tmp)
|
||||
f.MemoryRegister(asm.STORE, memory, tmp)
|
||||
return typ, err
|
||||
}
|
87
src/core/ExpressionToRegister.go
Normal file
87
src/core/ExpressionToRegister.go
Normal file
@ -0,0 +1,87 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// ExpressionToRegister puts the result of an expression into the specified register.
|
||||
func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) {
|
||||
f.SaveRegister(register)
|
||||
|
||||
if node.IsFolded {
|
||||
f.RegisterNumber(asm.MOVE, register, node.Value)
|
||||
return types.Int, nil
|
||||
}
|
||||
|
||||
if node.IsLeaf() {
|
||||
return f.TokenToRegister(node.Token, register)
|
||||
}
|
||||
|
||||
if ast.IsFunctionCall(node) {
|
||||
fn, err := f.CompileCall(node)
|
||||
|
||||
if register != f.CPU.Output[0] {
|
||||
f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0])
|
||||
}
|
||||
|
||||
if fn == nil || len(fn.ReturnTypes) == 0 {
|
||||
return types.Any, err
|
||||
}
|
||||
|
||||
return fn.ReturnTypes[0], err
|
||||
}
|
||||
|
||||
if node.Token.Kind == token.Array {
|
||||
array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes))
|
||||
offset, err := f.Number(node.Children[1].Token)
|
||||
f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register)
|
||||
return types.Int, err
|
||||
}
|
||||
|
||||
if len(node.Children) == 1 {
|
||||
if !node.Token.IsUnaryOperator() {
|
||||
return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End())
|
||||
}
|
||||
|
||||
typ, err := f.ExpressionToRegister(node.Children[0], register)
|
||||
|
||||
if err != nil {
|
||||
return typ, err
|
||||
}
|
||||
|
||||
return typ, f.ExecuteRegister(node.Token, register)
|
||||
}
|
||||
|
||||
left := node.Children[0]
|
||||
right := node.Children[1]
|
||||
final := register
|
||||
|
||||
if f.UsesRegister(right, register) {
|
||||
register = f.NewRegister()
|
||||
}
|
||||
|
||||
typ, err := f.ExpressionToRegister(left, register)
|
||||
|
||||
if err != nil {
|
||||
return types.Invalid, err
|
||||
}
|
||||
|
||||
if typ == types.Pointer && (node.Token.Kind == token.Add || node.Token.Kind == token.Sub) && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer {
|
||||
typ = types.Int
|
||||
}
|
||||
|
||||
err = f.Execute(node.Token, register, right)
|
||||
|
||||
if register != final {
|
||||
f.RegisterRegister(asm.MOVE, final, register)
|
||||
f.FreeRegister(register)
|
||||
}
|
||||
|
||||
return typ, err
|
||||
}
|
19
src/core/ExpressionsToRegisters.go
Normal file
19
src/core/ExpressionsToRegisters.go
Normal file
@ -0,0 +1,19 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
)
|
||||
|
||||
// ExpressionsToRegisters moves multiple expressions into the specified registers.
|
||||
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
|
||||
for i := len(expressions) - 1; i >= 0; i-- {
|
||||
_, err := f.ExpressionToRegister(expressions[i], registers[i])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
74
src/core/Fold.go
Normal file
74
src/core/Fold.go
Normal file
@ -0,0 +1,74 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/config"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Fold will try to precalculate the results of operations with constants.
|
||||
func (f *Function) Fold(expr *expression.Expression) error {
|
||||
if !config.ConstantFold {
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.IsLeaf() {
|
||||
if expr.Token.IsNumeric() {
|
||||
value, err := f.Number(expr.Token)
|
||||
expr.Value = value
|
||||
expr.IsFolded = true
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
canFold := true
|
||||
|
||||
for _, child := range expr.Children {
|
||||
f.Fold(child)
|
||||
|
||||
if !child.IsFolded {
|
||||
canFold = false
|
||||
}
|
||||
}
|
||||
|
||||
if !canFold || len(expr.Children) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
a := expr.Children[0].Value
|
||||
b := expr.Children[1].Value
|
||||
|
||||
switch expr.Token.Kind {
|
||||
case token.Or:
|
||||
expr.Value = a | b
|
||||
case token.And:
|
||||
expr.Value = a & b
|
||||
case token.Xor:
|
||||
expr.Value = a ^ b
|
||||
case token.Add:
|
||||
expr.Value = a + b
|
||||
case token.Sub:
|
||||
expr.Value = a - b
|
||||
case token.Mul:
|
||||
expr.Value = a * b
|
||||
case token.Div:
|
||||
expr.Value = a / b
|
||||
case token.Mod:
|
||||
expr.Value = a % b
|
||||
case token.Shl:
|
||||
expr.Value = a << b
|
||||
case token.Shr:
|
||||
expr.Value = a >> b
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
expr.IsFolded = true
|
||||
return nil
|
||||
}
|
35
src/core/Function.go
Normal file
35
src/core/Function.go
Normal file
@ -0,0 +1,35 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/fs"
|
||||
"git.akyoto.dev/cli/q/src/register"
|
||||
"git.akyoto.dev/cli/q/src/scope"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// Function represents the smallest unit of code.
|
||||
type Function struct {
|
||||
register.Machine
|
||||
Package string
|
||||
Name string
|
||||
UniqueName string
|
||||
File *fs.File
|
||||
Body token.List
|
||||
Parameters []*scope.Variable
|
||||
ReturnTypes []types.Type
|
||||
Functions map[string]*Function
|
||||
Err error
|
||||
deferred []func()
|
||||
count counter
|
||||
}
|
||||
|
||||
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||
type counter struct {
|
||||
assert int
|
||||
branch int
|
||||
multiBranch int
|
||||
data int
|
||||
loop int
|
||||
subBranch int
|
||||
}
|
13
src/core/IdentifierExists.go
Normal file
13
src/core/IdentifierExists.go
Normal file
@ -0,0 +1,13 @@
|
||||
package core
|
||||
|
||||
// IdentifierExists returns true if the identifier has been defined.
|
||||
func (f *Function) IdentifierExists(name string) bool {
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
_, exists := f.Functions[name]
|
||||
return exists
|
||||
}
|
24
src/core/JumpIfFalse.go
Normal file
24
src/core/JumpIfFalse.go
Normal file
@ -0,0 +1,24 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// JumpIfFalse jumps to the label if the previous comparison was false.
|
||||
func (f *Function) JumpIfFalse(operator token.Kind, label string) {
|
||||
switch operator {
|
||||
case token.Equal:
|
||||
f.Jump(asm.JNE, label)
|
||||
case token.NotEqual:
|
||||
f.Jump(asm.JE, label)
|
||||
case token.Greater:
|
||||
f.Jump(asm.JLE, label)
|
||||
case token.Less:
|
||||
f.Jump(asm.JGE, label)
|
||||
case token.GreaterEqual:
|
||||
f.Jump(asm.JL, label)
|
||||
case token.LessEqual:
|
||||
f.Jump(asm.JG, label)
|
||||
}
|
||||
}
|
24
src/core/JumpIfTrue.go
Normal file
24
src/core/JumpIfTrue.go
Normal file
@ -0,0 +1,24 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// JumpIfTrue jumps to the label if the previous comparison was true.
|
||||
func (f *Function) JumpIfTrue(operator token.Kind, label string) {
|
||||
switch operator {
|
||||
case token.Equal:
|
||||
f.Jump(asm.JE, label)
|
||||
case token.NotEqual:
|
||||
f.Jump(asm.JNE, label)
|
||||
case token.Greater:
|
||||
f.Jump(asm.JG, label)
|
||||
case token.Less:
|
||||
f.Jump(asm.JL, label)
|
||||
case token.GreaterEqual:
|
||||
f.Jump(asm.JGE, label)
|
||||
case token.LessEqual:
|
||||
f.Jump(asm.JLE, label)
|
||||
}
|
||||
}
|
38
src/core/NewFunction.go
Normal file
38
src/core/NewFunction.go
Normal file
@ -0,0 +1,38 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/fs"
|
||||
"git.akyoto.dev/cli/q/src/register"
|
||||
"git.akyoto.dev/cli/q/src/scope"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// NewFunction creates a new function.
|
||||
func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function {
|
||||
return &Function{
|
||||
Package: pkg,
|
||||
Name: name,
|
||||
UniqueName: pkg + "." + name,
|
||||
File: file,
|
||||
Body: body,
|
||||
Machine: register.Machine{
|
||||
Assembler: asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, 8),
|
||||
},
|
||||
Stack: scope.Stack{
|
||||
Scopes: []*scope.Scope{{}},
|
||||
},
|
||||
CPU: cpu.CPU{
|
||||
All: x64.AllRegisters,
|
||||
General: x64.GeneralRegisters,
|
||||
Input: x64.InputRegisters,
|
||||
Output: x64.OutputRegisters,
|
||||
SyscallInput: x64.SyscallInputRegisters,
|
||||
SyscallOutput: x64.SyscallOutputRegisters,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
54
src/core/Number.go
Normal file
54
src/core/Number.go
Normal file
@ -0,0 +1,54 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Number tries to convert the token into a numeric value.
|
||||
func (f *Function) Number(t token.Token) (int, error) {
|
||||
switch t.Kind {
|
||||
case token.Number:
|
||||
digits := t.Text(f.File.Bytes)
|
||||
|
||||
if strings.HasPrefix(digits, "0x") {
|
||||
number, err := strconv.ParseInt(digits[2:], 16, 64)
|
||||
return int(number), err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(digits, "0o") {
|
||||
number, err := strconv.ParseInt(digits[2:], 8, 64)
|
||||
return int(number), err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(digits, "0b") {
|
||||
number, err := strconv.ParseInt(digits[2:], 2, 64)
|
||||
return int(number), err
|
||||
}
|
||||
|
||||
number, err := strconv.Atoi(digits)
|
||||
return number, err
|
||||
|
||||
case token.Rune:
|
||||
r := t.Bytes(f.File.Bytes)
|
||||
r = String(r)
|
||||
|
||||
if len(r) == 0 {
|
||||
return 0, errors.New(errors.InvalidRune, f.File, t.Position+1)
|
||||
}
|
||||
|
||||
number, size := utf8.DecodeRune(r)
|
||||
|
||||
if len(r) > size {
|
||||
return 0, errors.New(errors.InvalidRune, f.File, t.Position+1)
|
||||
}
|
||||
|
||||
return int(number), nil
|
||||
}
|
||||
|
||||
return 0, errors.New(errors.InvalidNumber, f.File, t.Position)
|
||||
}
|
51
src/core/PrintInstructions.go
Normal file
51
src/core/PrintInstructions.go
Normal file
@ -0,0 +1,51 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/go/color/ansi"
|
||||
)
|
||||
|
||||
// PrintInstructions shows the assembly instructions.
|
||||
func (f *Function) PrintInstructions() {
|
||||
ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
|
||||
|
||||
for i, x := range f.Assembler.Instructions {
|
||||
ansi.Dim.Print("│ ")
|
||||
|
||||
switch x.Mnemonic {
|
||||
case asm.LABEL:
|
||||
ansi.Yellow.Printf("%-44s", x.Data.String()+":")
|
||||
|
||||
case asm.COMMENT:
|
||||
ansi.Dim.Printf("%-44s", x.Data.String())
|
||||
|
||||
default:
|
||||
ansi.Green.Printf("%-12s", x.Mnemonic.String())
|
||||
|
||||
if x.Data != nil {
|
||||
fmt.Printf("%-32s", x.Data.String())
|
||||
} else {
|
||||
fmt.Printf("%-32s", "")
|
||||
}
|
||||
}
|
||||
|
||||
registers := bytes.Buffer{}
|
||||
used := f.RegisterHistory[i]
|
||||
|
||||
for _, reg := range f.CPU.All {
|
||||
if used&(1<<reg) != 0 {
|
||||
registers.WriteString("⬤ ")
|
||||
} else {
|
||||
registers.WriteString("◯ ")
|
||||
}
|
||||
}
|
||||
|
||||
ansi.Dim.Print(registers.String())
|
||||
ansi.Dim.Print(" │\n")
|
||||
}
|
||||
|
||||
ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯")
|
||||
}
|
43
src/core/String.go
Normal file
43
src/core/String.go
Normal file
@ -0,0 +1,43 @@
|
||||
package core
|
||||
|
||||
import "bytes"
|
||||
|
||||
// String replaces the escape sequences in the contents of a string token with the respective characters.
|
||||
func String(data []byte) []byte {
|
||||
data = data[1 : len(data)-1]
|
||||
escape := bytes.IndexByte(data, '\\')
|
||||
|
||||
if escape == -1 {
|
||||
return data
|
||||
}
|
||||
|
||||
tmp := make([]byte, 0, len(data))
|
||||
|
||||
for {
|
||||
tmp = append(tmp, data[:escape]...)
|
||||
|
||||
switch data[escape+1] {
|
||||
case '0':
|
||||
tmp = append(tmp, '\000')
|
||||
case 't':
|
||||
tmp = append(tmp, '\t')
|
||||
case 'n':
|
||||
tmp = append(tmp, '\n')
|
||||
case 'r':
|
||||
tmp = append(tmp, '\r')
|
||||
case '"':
|
||||
tmp = append(tmp, '"')
|
||||
case '\'':
|
||||
tmp = append(tmp, '\'')
|
||||
case '\\':
|
||||
tmp = append(tmp, '\\')
|
||||
}
|
||||
|
||||
data = data[escape+2:]
|
||||
escape = bytes.IndexByte(data, '\\')
|
||||
|
||||
if escape == -1 {
|
||||
return tmp
|
||||
}
|
||||
}
|
||||
}
|
50
src/core/TokenToRegister.go
Normal file
50
src/core/TokenToRegister.go
Normal file
@ -0,0 +1,50 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
"git.akyoto.dev/cli/q/src/types"
|
||||
)
|
||||
|
||||
// TokenToRegister moves a token into a register.
|
||||
// It only works with identifiers, numbers and strings.
|
||||
func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) {
|
||||
switch t.Kind {
|
||||
case token.Identifier:
|
||||
name := t.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
||||
}
|
||||
|
||||
f.UseVariable(variable)
|
||||
f.SaveRegister(register)
|
||||
f.RegisterRegister(asm.MOVE, register, variable.Register)
|
||||
return variable.Type, nil
|
||||
|
||||
case token.Number, token.Rune:
|
||||
number, err := f.Number(t)
|
||||
|
||||
if err != nil {
|
||||
return types.Invalid, err
|
||||
}
|
||||
|
||||
f.SaveRegister(register)
|
||||
f.RegisterNumber(asm.MOVE, register, number)
|
||||
return types.Int, nil
|
||||
|
||||
case token.String:
|
||||
data := t.Bytes(f.File.Bytes)
|
||||
data = String(data)
|
||||
label := f.AddBytes(data)
|
||||
f.SaveRegister(register)
|
||||
f.RegisterLabel(asm.MOVE, register, label)
|
||||
return types.Pointer, nil
|
||||
|
||||
default:
|
||||
return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position)
|
||||
}
|
||||
}
|
40
src/core/UsesRegister.go
Normal file
40
src/core/UsesRegister.go
Normal file
@ -0,0 +1,40 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/ast"
|
||||
"git.akyoto.dev/cli/q/src/cpu"
|
||||
"git.akyoto.dev/cli/q/src/expression"
|
||||
)
|
||||
|
||||
// UsesRegister returns true if evaluating the expression would write or read the given register.
|
||||
func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool {
|
||||
if expr.IsLeaf() {
|
||||
return false
|
||||
}
|
||||
|
||||
if ast.IsFunctionCall(expr) {
|
||||
if register == f.CPU.Output[0] {
|
||||
return true
|
||||
}
|
||||
|
||||
for i, parameter := range expr.Children[1:] {
|
||||
if register == f.CPU.Input[i] {
|
||||
return true
|
||||
}
|
||||
|
||||
if f.UsesRegister(parameter, register) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for _, child := range expr.Children {
|
||||
if f.UsesRegister(child, register) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user