Implemented source tracking and type checking
All checks were successful
/ test (push) Successful in 21s
All checks were successful
/ test (push) Successful in 21s
This commit is contained in:
parent
70c2da4a4d
commit
329fcfff6f
30 changed files with 427 additions and 125 deletions
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue