Improved type system

This commit is contained in:
Eduard Urbach 2024-08-08 12:55:25 +02:00
parent d624a5f895
commit 5d9be01a85
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
20 changed files with 111 additions and 67 deletions

View File

@ -22,7 +22,7 @@ munmap(address Pointer, length Int) -> Int {
return syscall(11, address, length) return syscall(11, address, length)
} }
clone(flags Int, stack Pointer) -> ThreadID { clone(flags Int, stack Pointer) -> Int {
return syscall(56, flags, stack) return syscall(56, flags, stack)
} }

View File

@ -1,11 +1,10 @@
package core package core
import ( import (
"strings"
"git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/errors"
"git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/expression"
"git.akyoto.dev/cli/q/src/types"
) )
// CompileCall executes a function call. // CompileCall executes a function call.
@ -18,7 +17,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
nameNode = root.Children[0] nameNode = root.Children[0]
fn *Function fn *Function
name string name string
fullName string
exists bool exists bool
) )
@ -47,12 +45,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
imp.Used = true imp.Used = true
} }
tmp := strings.Builder{} fn, exists = f.Functions[pkg+"."+name]
tmp.WriteString(pkg)
tmp.WriteString(".")
tmp.WriteString(name)
fullName = tmp.String()
fn, exists = f.Functions[fullName]
if !exists { if !exists {
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
@ -68,10 +61,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
return nil, err return nil, err
} }
if typ != fn.Parameters[i].Type { if !types.Check(typ, fn.Parameters[i].Type) {
return nil, errors.New(&errors.TypeMismatch{ return nil, errors.New(&errors.TypeMismatch{
Encountered: string(typ), Encountered: typ.Name,
Expected: string(fn.Parameters[i].Type), Expected: fn.Parameters[i].Type.Name,
ParameterName: fn.Parameters[i].Name, ParameterName: fn.Parameters[i].Name,
}, f.File, parameters[i].Token.Position) }, f.File, parameters[i].Token.Position)
} }
@ -87,7 +80,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
} }
} }
f.Call(fullName) f.Call(fn.UniqueName)
for _, register := range registers { for _, register := range registers {
if register == f.CPU.Output[0] && root.Parent != nil { if register == f.CPU.Output[0] && root.Parent != nil {

View File

@ -5,7 +5,6 @@ import (
"git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/ast"
"git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/errors"
"git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/expression"
"git.akyoto.dev/cli/q/src/types"
) )
// CompileDefinition compiles a variable definition. // CompileDefinition compiles a variable definition.
@ -28,7 +27,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
variable.Type = typ variable.Type = typ
if variable.Type == types.Invalid { if variable.Type == nil {
return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) return errors.New(errors.UnknownType, f.File, node.Expression.Token.End())
} }

View File

@ -21,10 +21,10 @@ func (f *Function) CompileReturn(node *ast.Return) error {
return err return err
} }
if typ != types.Any && typ != f.ReturnTypes[i] { if !types.Check(typ, f.ReturnTypes[i]) {
return errors.New(&errors.TypeMismatch{ return errors.New(&errors.TypeMismatch{
Encountered: string(typ), Encountered: typ.Name,
Expected: string(f.ReturnTypes[i]), Expected: f.ReturnTypes[i].Name,
ParameterName: "", ParameterName: "",
IsReturn: true, IsReturn: true,
}, f.File, node.Values[i].Token.Position) }, f.File, node.Values[i].Token.Position)

View File

@ -8,7 +8,7 @@ import (
) )
// Evaluate evaluates an expression and returns a register that contains the value of the expression. // 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) { func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, error) {
if expr.Token.Kind == token.Identifier { if expr.Token.Kind == token.Identifier {
name := expr.Token.Text(f.File.Bytes) name := expr.Token.Text(f.File.Bytes)
variable := f.VariableByName(name) variable := f.VariableByName(name)

View File

@ -9,18 +9,18 @@ import (
) )
// ExpressionToMemory puts the result of an expression into the specified memory address. // 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) { func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) {
if node.IsLeaf() && node.Token.IsNumeric() { if node.IsLeaf() && node.Token.IsNumeric() {
number, err := f.Number(node.Token) number, err := f.Number(node.Token)
if err != nil { if err != nil {
return types.Invalid, err return nil, err
} }
size := byte(sizeof.Signed(int64(number))) size := byte(sizeof.Signed(int64(number)))
if size != memory.Length { if size != memory.Length {
return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position)
} }
f.MemoryNumber(asm.STORE, memory, number) f.MemoryNumber(asm.STORE, memory, number)

View File

@ -11,7 +11,7 @@ import (
) )
// ExpressionToRegister puts the result of an expression into the specified register. // ExpressionToRegister puts the result of an expression into the specified register.
func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (*types.Type, error) {
f.SaveRegister(register) f.SaveRegister(register)
if node.IsFolded { if node.IsFolded {
@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
} }
if fn == nil || len(fn.ReturnTypes) == 0 { if fn == nil || len(fn.ReturnTypes) == 0 {
return types.Any, err return nil, err
} }
return fn.ReturnTypes[0], err return fn.ReturnTypes[0], err
@ -46,7 +46,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
if len(node.Children) == 1 { if len(node.Children) == 1 {
if !node.Token.IsUnaryOperator() { if !node.Token.IsUnaryOperator() {
return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End()) return nil, errors.New(errors.MissingOperand, f.File, node.Token.End())
} }
typ, err := f.ExpressionToRegister(node.Children[0], register) typ, err := f.ExpressionToRegister(node.Children[0], register)
@ -69,10 +69,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
typ, err := f.ExpressionToRegister(left, register) typ, err := f.ExpressionToRegister(left, register)
if err != nil { if err != nil {
return types.Invalid, err return nil, 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 { if typ == types.Pointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer {
typ = types.Int typ = types.Int
} }

View File

@ -17,7 +17,7 @@ type Function struct {
File *fs.File File *fs.File
Body token.List Body token.List
Parameters []*scope.Variable Parameters []*scope.Variable
ReturnTypes []types.Type ReturnTypes []*types.Type
Functions map[string]*Function Functions map[string]*Function
Err error Err error
deferred []func() deferred []func()

View File

@ -10,14 +10,14 @@ import (
// TokenToRegister moves a token into a register. // TokenToRegister moves a token into a register.
// It only works with identifiers, numbers and strings. // It only works with identifiers, numbers and strings.
func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types.Type, error) {
switch t.Kind { switch t.Kind {
case token.Identifier: case token.Identifier:
name := t.Text(f.File.Bytes) name := t.Text(f.File.Bytes)
variable := f.VariableByName(name) variable := f.VariableByName(name)
if variable == nil { if variable == nil {
return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
} }
f.UseVariable(variable) f.UseVariable(variable)
@ -29,7 +29,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.
number, err := f.Number(t) number, err := f.Number(t)
if err != nil { if err != nil {
return types.Invalid, err return nil, err
} }
f.SaveRegister(register) f.SaveRegister(register)
@ -45,6 +45,6 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.
return types.Pointer, nil return types.Pointer, nil
default: default:
return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) return nil, errors.New(errors.InvalidExpression, f.File, t.Position)
} }
} }

View File

@ -233,7 +233,7 @@ func (s *Scanner) scanFile(path string, pkg string) error {
typeEnd-- typeEnd--
} }
function.ReturnTypes = types.NewList(tokens[typeStart:typeEnd], contents) function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], contents)
} }
parameters := tokens[paramsStart:paramsEnd] parameters := tokens[paramsStart:paramsEnd]
@ -245,7 +245,7 @@ func (s *Scanner) scanFile(path string, pkg string) error {
} }
name := tokens[0].Text(contents) name := tokens[0].Text(contents)
dataType := types.New(tokens[1:].Text(contents)) dataType := types.Parse(tokens[1:].Text(contents))
register := x64.InputRegisters[count] register := x64.InputRegisters[count]
uses := token.Count(function.Body, contents, token.Identifier, name) uses := token.Count(function.Body, contents, token.Identifier, name)

View File

@ -7,8 +7,8 @@ import (
// Variable represents a named register. // Variable represents a named register.
type Variable struct { type Variable struct {
Type *types.Type
Name string Name string
Type types.Type
Alive uint8 Alive uint8
Register cpu.Register Register cpu.Register
} }

16
src/types/Base.go Normal file
View File

@ -0,0 +1,16 @@
package types
var (
Float64 = &Type{Name: "Float64", Size: 8}
Float32 = &Type{Name: "Float32", Size: 4}
Int64 = &Type{Name: "Int64", Size: 8}
Int32 = &Type{Name: "Int32", Size: 4}
Int16 = &Type{Name: "Int16", Size: 2}
Int8 = &Type{Name: "Int8", Size: 1}
Pointer = &Type{Name: "Pointer", Size: 8}
)
var (
Float = Float64
Int = Int64
)

6
src/types/Check.go Normal file
View File

@ -0,0 +1,6 @@
package types
// Check returns true if the first type can be converted into the second type.
func Check(a *Type, b *Type) bool {
return a == nil || a == b
}

11
src/types/Field.go Normal file
View File

@ -0,0 +1,11 @@
package types
import "git.akyoto.dev/cli/q/src/token"
// Field is a field in a data structure.
type Field struct {
Type *Type
Name string
Position token.Position
Offset uint8
}

View File

@ -1,6 +0,0 @@
package types
// New creates a new type from a list of tokens.
func New(name string) Type {
return Type(name)
}

View File

@ -1,16 +0,0 @@
package types
import "git.akyoto.dev/cli/q/src/token"
// NewList generates a list of types from comma separated tokens.
func NewList(tokens token.List, source []byte) []Type {
var list []Type
tokens.Split(func(parameter token.List) error {
typ := New(parameter.Text(source))
list = append(list, typ)
return nil
})
return list
}

27
src/types/Parse.go Normal file
View File

@ -0,0 +1,27 @@
package types
// Parse creates a new type from a list of tokens.
func Parse(name string) *Type {
switch name {
case "Int":
return Int
case "Int64":
return Int64
case "Int32":
return Int32
case "Int16":
return Int16
case "Int8":
return Int8
case "Float":
return Float
case "Float64":
return Float64
case "Float32":
return Float32
case "Pointer":
return Pointer
default:
panic("Unknown type " + name)
}
}

16
src/types/ParseList.go Normal file
View File

@ -0,0 +1,16 @@
package types
import "git.akyoto.dev/cli/q/src/token"
// ParseList generates a list of types from comma separated tokens.
func ParseList(tokens token.List, source []byte) []*Type {
var list []*Type
tokens.Split(func(parameter token.List) error {
typ := Parse(parameter.Text(source))
list = append(list, typ)
return nil
})
return list
}

View File

@ -1,10 +1,8 @@
package types package types
type Type string // Type represents a type in the type system.
type Type struct {
const ( Name string
Invalid = "" Fields []*Field
Any = "Any" Size uint8
Int = "Int" }
Pointer = "Pointer"
)

View File

@ -36,7 +36,7 @@ var errs = []struct {
{"MissingOperand.q", errors.MissingOperand}, {"MissingOperand.q", errors.MissingOperand},
{"MissingOperand2.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand},
{"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}},
{"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}},
{"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}},
{"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}},
{"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}},