Implemented source tracking and type checking
All checks were successful
/ test (push) Successful in 21s

This commit is contained in:
Eduard Urbach 2025-06-27 00:05:16 +02:00
parent 70c2da4a4d
commit 329fcfff6f
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
30 changed files with 427 additions and 125 deletions

View file

@ -8,7 +8,7 @@ import (
func (f *Function) CheckDeadCode() error {
for instr := range f.Values {
if instr.IsConst() && instr.Alive() == 0 {
return errors.New(&UnusedValue{Value: instr.String()}, f.File, instr.Token().Position)
return errors.New(&UnusedValue{Value: instr.String()}, f.File, instr.Start())
}
}

View file

@ -6,7 +6,7 @@ import (
"git.urbach.dev/cli/q/src/asm"
"git.urbach.dev/cli/q/src/cpu"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Compile turns a function into machine code.
@ -18,12 +18,35 @@ func (f *Function) Compile() {
continue
}
f.Identifiers[input.Name] = f.AppendRegister(i + extra)
array, isArray := input.Typ.(*types.Array)
if input.TypeTokens[0].Kind == token.ArrayStart {
if isArray {
pointer := &ssa.Parameter{
Index: uint8(i + extra),
Name: input.Name,
Typ: &types.Pointer{To: array.Of},
Source: input.Source,
}
f.Append(pointer)
f.Identifiers[pointer.Name] = pointer
extra++
f.Identifiers[input.Name+".length"] = f.AppendRegister(i + extra)
length := &ssa.Parameter{
Index: uint8(i + extra),
Name: input.Name + ".len",
Typ: types.AnyInt,
Source: input.Source,
}
f.Append(length)
f.Identifiers[length.Name] = length
continue
}
input.Index = uint8(i + extra)
f.Append(input)
f.Identifiers[input.Name] = input
}
for instr := range f.Body.Instructions {
@ -49,15 +72,15 @@ func (f *Function) Compile() {
for instr := range f.Values {
switch instr := instr.(type) {
case *ssa.Call:
f.mv(instr.Args[1:], f.CPU.Call)
f.mv(instr.Arguments[1:], f.CPU.Call)
switch arg := instr.Args[0].(type) {
switch arg := instr.Arguments[0].(type) {
case *ssa.Function:
f.Assembler.Instructions = append(f.Assembler.Instructions, &asm.Call{Label: arg.UniqueName})
}
case *ssa.Syscall:
f.mv(instr.Args, f.CPU.Syscall)
f.mv(instr.Arguments, f.CPU.Syscall)
f.Assembler.Append(&asm.Syscall{})
case *ssa.Return:

View file

@ -16,9 +16,8 @@ func (f *Function) CompileReturn(tokens token.List) error {
}
f.Append(&ssa.Return{
Arguments: ssa.Arguments{
Args: []ssa.Value{value},
},
Arguments: []ssa.Value{value},
Source: ssa.Source(tokens),
})
return nil

View file

@ -8,6 +8,7 @@ import (
"git.urbach.dev/cli/q/src/expression"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Evaluate converts an expression to an SSA value.
@ -26,8 +27,8 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
f.Dependencies.Add(function)
v := f.AppendFunction(function.UniqueName)
v.Source = expr.Token
v := f.AppendFunction(function.UniqueName, function.Type)
v.Source = ssa.Source(expr.Source)
return v, nil
}
@ -41,14 +42,14 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
v := f.AppendInt(number)
v.Source = expr.Token
v.Source = ssa.Source(expr.Source)
return v, nil
case token.String:
data := expr.Token.Bytes(f.File.Bytes)
data = Unescape(data)
v := f.AppendBytes(data)
v.Source = expr.Token
v.Source = ssa.Source(expr.Source)
return v, nil
}
@ -65,7 +66,13 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
if funcName == "len" {
identifier := children[1].String(f.File.Bytes)
return f.Identifiers[identifier+".length"], nil
length, exists := f.Identifiers[identifier+".len"]
if !exists {
return nil, errors.New(&UnknownIdentifier{Name: identifier + ".len"}, f.File, expr.Token.Position)
}
return length, nil
}
if funcName == "syscall" {
@ -87,19 +94,37 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
if isSyscall {
v := f.Append(&ssa.Syscall{
Arguments: ssa.Arguments{Args: args},
HasToken: ssa.HasToken{Source: expr.Token},
})
syscall := &ssa.Syscall{
Arguments: args,
Source: ssa.Source(expr.Source),
}
return v, nil
return f.Append(syscall), nil
} else {
v := f.Append(&ssa.Call{
Arguments: ssa.Arguments{Args: args},
HasToken: ssa.HasToken{Source: expr.Token},
})
name := args[0].(*ssa.Function).UniqueName
fn := f.All.Functions[name]
parameters := args[1:]
return v, nil
if len(parameters) != len(fn.Input) {
return nil, errors.New(&ParameterCountMismatch{Function: name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, expr.Source[0].Position)
}
for i, param := range parameters {
if !types.Is(param.Type(), fn.Input[i].Typ) {
return nil, errors.New(&TypeMismatch{
Encountered: param.Type().Name(),
Expected: fn.Input[i].Typ.Name(),
ParameterName: fn.Input[i].Name,
}, f.File, param.Start())
}
}
call := &ssa.Call{
Arguments: args,
Source: ssa.Source(expr.Source),
}
return f.Append(call), nil
}
case token.Dot:
@ -113,8 +138,8 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
f.Dependencies.Add(function)
v := f.AppendFunction(function.UniqueName)
v.Source = expr.Children[1].Token
v := f.AppendFunction(function.UniqueName, function.Type)
v.Source = ssa.Source(expr.Source)
return v, nil
}

View file

@ -9,6 +9,7 @@ import (
"git.urbach.dev/cli/q/src/set"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Function is the smallest unit of code.
@ -17,14 +18,15 @@ type Function struct {
Name string
UniqueName string
File *fs.File
Input []*Parameter
Output []*Parameter
Input []*ssa.Parameter
Output []*ssa.Parameter
Body token.List
Identifiers map[string]ssa.Value
All *Environment
Dependencies set.Ordered[*Function]
Assembler asm.Assembler
CPU *cpu.CPU
Type *types.Function
Err error
}

View file

@ -18,8 +18,4 @@ func TestFunction(t *testing.T) {
assert.False(t, main.IsExtern())
assert.Equal(t, main.UniqueName, "main.main")
assert.Equal(t, main.String(), main.UniqueName)
write, exists := env.Functions["io.write"]
assert.True(t, exists)
write.Output[0].Type()
}

View file

@ -1,18 +0,0 @@
package core
import (
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Parameter is an input or output parameter in a function.
type Parameter struct {
Name string
TypeTokens token.List
typ types.Type
}
// Type returns the data type of the parameter.
func (p *Parameter) Type() types.Type {
return p.typ
}

View file

@ -12,6 +12,43 @@ var (
InvalidRune = errors.String("Invalid rune")
)
// ParameterCountMismatch error is created when the number of provided parameters doesn't match the function signature.
type ParameterCountMismatch struct {
Function string
Count int
ExpectedCount int
}
func (err *ParameterCountMismatch) Error() string {
if err.Count > err.ExpectedCount {
return fmt.Sprintf("Too many parameters in '%s' function call", err.Function)
}
return fmt.Sprintf("Not enough parameters in '%s' function call", err.Function)
}
// TypeMismatch represents an error where a type requirement was not met.
type TypeMismatch struct {
Encountered string
Expected string
ParameterName string
IsReturn bool
}
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)
}
// UnknownIdentifier represents unknown identifiers.
type UnknownIdentifier struct {
Name string