Implemented type checks
This commit is contained in:
parent
6e848774ed
commit
1b13539b22
@ -14,7 +14,7 @@ close(fd Int) -> Int {
|
||||
return syscall(3, fd)
|
||||
}
|
||||
|
||||
mmap(address Int, length Int, protection Int, flags Int) -> Int {
|
||||
mmap(address Int, length Int, protection Int, flags Int) -> Pointer {
|
||||
return syscall(9, address, length, protection, flags)
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,9 @@ const (
|
||||
|
||||
var (
|
||||
AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15}
|
||||
SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9}
|
||||
SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9}
|
||||
SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11}
|
||||
GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15}
|
||||
CallRegisters = SyscallRegisters
|
||||
ReturnValueRegisters = SyscallRegisters
|
||||
InputRegisters = SyscallInputRegisters
|
||||
OutputRegisters = SyscallInputRegisters
|
||||
)
|
||||
|
@ -32,13 +32,13 @@ func (r *Result) finalize() ([]byte, []byte) {
|
||||
}
|
||||
|
||||
final.Call("main.main")
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
|
||||
final.Syscall()
|
||||
|
||||
final.Label(asm.LABEL, "_crash")
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
|
||||
final.Syscall()
|
||||
|
||||
// This will place the main function immediately after the entry point
|
||||
|
@ -15,32 +15,33 @@ import (
|
||||
func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
|
||||
var (
|
||||
pkg = f.Package
|
||||
nameRoot = root.Children[0]
|
||||
nameNode = root.Children[0]
|
||||
fn *Function
|
||||
name string
|
||||
fullName string
|
||||
exists bool
|
||||
)
|
||||
|
||||
if nameRoot.IsLeaf() {
|
||||
name = nameRoot.Token.Text(f.File.Bytes)
|
||||
if nameNode.IsLeaf() {
|
||||
name = nameNode.Token.Text(f.File.Bytes)
|
||||
|
||||
if name == "syscall" {
|
||||
return nil, f.CompileSyscall(root)
|
||||
}
|
||||
} else {
|
||||
pkg = nameRoot.Children[0].Token.Text(f.File.Bytes)
|
||||
name = nameRoot.Children[1].Token.Text(f.File.Bytes)
|
||||
pkg = nameNode.Children[0].Token.Text(f.File.Bytes)
|
||||
name = nameNode.Children[1].Token.Text(f.File.Bytes)
|
||||
}
|
||||
|
||||
isSyscall := name == "syscall"
|
||||
|
||||
if !isSyscall {
|
||||
if pkg != f.File.Package {
|
||||
if f.File.Imports == nil {
|
||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position)
|
||||
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, nameRoot.Token.Position)
|
||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
imp.Used = true
|
||||
@ -54,41 +55,40 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
|
||||
fn, exists = f.Functions[fullName]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position)
|
||||
}
|
||||
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
parameters := root.Children[1:]
|
||||
registers := f.CPU.Input[:len(parameters)]
|
||||
|
||||
if isSyscall {
|
||||
registers = f.CPU.Syscall[:len(parameters)]
|
||||
}
|
||||
|
||||
err := f.ExpressionsToRegisters(parameters, registers)
|
||||
for i := len(parameters) - 1; i >= 0; i-- {
|
||||
typ, err := f.ExpressionToRegister(parameters[i], registers[i])
|
||||
|
||||
if err != nil {
|
||||
return fn, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Save all return value registers of the function
|
||||
f.SaveRegister(f.CPU.Output[0])
|
||||
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)
|
||||
}
|
||||
|
||||
// Push
|
||||
for _, register := range f.CPU.General {
|
||||
if f.RegisterIsUsed(register) {
|
||||
f.Register(asm.PUSH, register)
|
||||
}
|
||||
}
|
||||
|
||||
// Call
|
||||
if isSyscall {
|
||||
f.Syscall()
|
||||
} else {
|
||||
f.Call(fullName)
|
||||
}
|
||||
|
||||
// Free parameter registers
|
||||
for _, register := range registers {
|
||||
if register == f.CPU.Output[0] && root.Parent != nil {
|
||||
continue
|
||||
@ -97,7 +97,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
|
||||
f.FreeRegister(register)
|
||||
}
|
||||
|
||||
// Pop
|
||||
for i := len(f.CPU.General) - 1; i >= 0; i-- {
|
||||
register := f.CPU.General[i]
|
||||
|
||||
|
@ -41,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
|
||||
}
|
||||
|
||||
count := 0
|
||||
_, err := f.CompileCall(right)
|
||||
called, err := f.CompileCall(right)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@ -54,6 +54,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if called != nil {
|
||||
variable.Type = called.ReturnTypes[count]
|
||||
}
|
||||
|
||||
f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count])
|
||||
f.AddVariable(variable)
|
||||
count++
|
||||
|
@ -2,6 +2,8 @@ package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/types"
|
||||
)
|
||||
|
||||
// CompileReturn compiles a return instruction.
|
||||
@ -12,5 +14,22 @@ func (f *Function) CompileReturn(node *ast.Return) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f.ExpressionsToRegisters(node.Values, f.CPU.Output)
|
||||
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
|
||||
}
|
||||
|
32
src/build/core/CompileSyscall.go
Normal file
32
src/build/core/CompileSyscall.go
Normal file
@ -0,0 +1,32 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/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
|
||||
}
|
@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
|
||||
}
|
||||
|
||||
if fn == nil || len(fn.ReturnTypes) == 0 {
|
||||
return types.Invalid, err
|
||||
return types.Any, err
|
||||
}
|
||||
|
||||
return fn.ReturnTypes[0], err
|
||||
@ -72,6 +72,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
|
||||
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 {
|
||||
|
@ -27,10 +27,11 @@ func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Fu
|
||||
},
|
||||
CPU: cpu.CPU{
|
||||
All: x64.AllRegisters,
|
||||
Input: x64.CallRegisters,
|
||||
General: x64.GeneralRegisters,
|
||||
Syscall: x64.SyscallRegisters,
|
||||
Output: x64.ReturnValueRegisters,
|
||||
Input: x64.InputRegisters,
|
||||
Output: x64.OutputRegisters,
|
||||
SyscallInput: x64.SyscallInputRegisters,
|
||||
SyscallOutput: x64.SyscallOutputRegisters,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.
|
||||
label := f.AddBytes(data)
|
||||
f.SaveRegister(register)
|
||||
f.RegisterLabel(asm.MOVE, register, label)
|
||||
return types.Int, nil
|
||||
return types.Pointer, nil
|
||||
|
||||
default:
|
||||
return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position)
|
||||
|
@ -4,7 +4,8 @@ package cpu
|
||||
type CPU struct {
|
||||
All []Register
|
||||
General []Register
|
||||
Syscall []Register
|
||||
Input []Register
|
||||
Output []Register
|
||||
SyscallInput []Register
|
||||
SyscallOutput []Register
|
||||
}
|
||||
|
26
src/build/errors/TypeMismatch.go
Normal file
26
src/build/errors/TypeMismatch.go
Normal file
@ -0,0 +1,26 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// TypeMismatch represents an error where a type requirement was not met.
|
||||
type TypeMismatch struct {
|
||||
Encountered string
|
||||
Expected string
|
||||
ParameterName string
|
||||
IsReturn bool
|
||||
}
|
||||
|
||||
// Error generates the string representation.
|
||||
func (err *TypeMismatch) Error() string {
|
||||
subject := "type"
|
||||
|
||||
if err.IsReturn {
|
||||
subject = "return type"
|
||||
}
|
||||
|
||||
if err.ParameterName != "" {
|
||||
return fmt.Sprintf("Expected parameter '%s' of %s '%s' (encountered '%s')", err.ParameterName, subject, err.Expected, err.Encountered)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Expected %s '%s' instead of '%s'", subject, err.Expected, err.Encountered)
|
||||
}
|
@ -229,7 +229,12 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
||||
function := core.NewFunction(pkg, name, file, body)
|
||||
|
||||
if typeStart != -1 {
|
||||
function.ReturnTypes = append(function.ReturnTypes, types.New(tokens[typeStart:typeEnd]))
|
||||
if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd {
|
||||
typeStart++
|
||||
typeEnd--
|
||||
}
|
||||
|
||||
function.ReturnTypes = types.NewList(tokens[typeStart:typeEnd], contents)
|
||||
}
|
||||
|
||||
parameters := tokens[paramsStart:paramsEnd]
|
||||
@ -241,8 +246,8 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
||||
}
|
||||
|
||||
name := tokens[0].Text(contents)
|
||||
dataType := types.New(tokens[1:])
|
||||
register := x64.CallRegisters[count]
|
||||
dataType := types.New(tokens[1:].Text(contents))
|
||||
register := x64.InputRegisters[count]
|
||||
uses := token.Count(function.Body, contents, token.Identifier, name)
|
||||
|
||||
if uses == 0 {
|
||||
|
@ -47,6 +47,7 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope {
|
||||
Name: v.Name,
|
||||
Register: v.Register,
|
||||
Alive: count,
|
||||
Type: v.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package types
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/token"
|
||||
|
||||
// New creates a new type from a list of tokens.
|
||||
func New(tokens token.List) Type {
|
||||
return Int
|
||||
func New(name string) Type {
|
||||
return Type(name)
|
||||
}
|
||||
|
19
src/build/types/NewList.go
Normal file
19
src/build/types/NewList.go
Normal file
@ -0,0 +1,19 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// NewList generates a list of types from comma separated tokens.
|
||||
func NewList(tokens token.List, source []byte) []Type {
|
||||
var list []Type
|
||||
|
||||
expression.EachParameter(tokens, func(parameter token.List) error {
|
||||
typ := New(parameter.Text(source))
|
||||
list = append(list, typ)
|
||||
return nil
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package types
|
||||
|
||||
type Type int
|
||||
type Type string
|
||||
|
||||
const (
|
||||
Invalid Type = iota // Invalid is an invalid type.
|
||||
Int
|
||||
Invalid = ""
|
||||
Any = "Any"
|
||||
Int = "Int"
|
||||
Pointer = "Pointer"
|
||||
)
|
||||
|
7
tests/errors/TypeMismatch.q
Normal file
7
tests/errors/TypeMismatch.q
Normal file
@ -0,0 +1,7 @@
|
||||
main() {
|
||||
writeToMemory(0)
|
||||
}
|
||||
|
||||
writeToMemory(p Pointer) {
|
||||
p[0] = 'A'
|
||||
}
|
@ -36,6 +36,7 @@ var errs = []struct {
|
||||
{"MissingOperand.q", errors.MissingOperand},
|
||||
{"MissingOperand2.q", errors.MissingOperand},
|
||||
{"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}},
|
||||
{"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}},
|
||||
{"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}},
|
||||
{"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}},
|
||||
{"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}},
|
||||
|
Loading…
Reference in New Issue
Block a user