From 329fcfff6f100bfb592663655b4212f9376bedeb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 27 Jun 2025 00:05:16 +0200 Subject: [PATCH] Implemented source tracking and type checking --- src/compiler/Compile.go | 35 +++++++++++++++++- src/compiler/errors.go | 1 + src/compiler/showSSA.go | 16 +++++--- src/core/CheckDeadCode.go | 2 +- src/core/Compile.go | 37 +++++++++++++++---- src/core/CompileReturn.go | 5 +-- src/core/Evaluate.go | 59 +++++++++++++++++++++--------- src/core/Function.go | 6 ++- src/core/Function_test.go | 4 -- src/core/Parameter.go | 18 --------- src/core/errors.go | 37 +++++++++++++++++++ src/expression/Expression.go | 1 + src/expression/Parse.go | 19 +++++++++- src/scanner/scanSignature.go | 20 +++++----- src/ssa/Arguments.go | 14 +++---- src/ssa/BinaryOperation.go | 11 +++--- src/ssa/Bytes.go | 15 ++++++-- src/ssa/Call.go | 20 ++++++++-- src/ssa/Function.go | 9 ++++- src/ssa/HasToken.go | 11 ------ src/ssa/IR.go | 13 ++----- src/ssa/Int.go | 8 +++- src/ssa/Parameter.go | 16 ++++++-- src/ssa/Return.go | 20 +++++++--- src/ssa/Source.go | 13 +++++++ src/ssa/Syscall.go | 14 +++++-- src/ssa/Value.go | 9 ++++- src/types/Common.go | 1 + src/types/Function.go | 47 ++++++++++++++++++++++++ src/types/Parse.go | 71 ++++++++++++++++++++++++++++++++++++ 30 files changed, 427 insertions(+), 125 deletions(-) delete mode 100644 src/core/Parameter.go delete mode 100644 src/ssa/HasToken.go create mode 100644 src/ssa/Source.go create mode 100644 src/types/Function.go create mode 100644 src/types/Parse.go diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 91e908c..616f82c 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -6,6 +6,7 @@ import ( "git.urbach.dev/cli/q/src/build" "git.urbach.dev/cli/q/src/core" "git.urbach.dev/cli/q/src/scanner" + "git.urbach.dev/cli/q/src/types" ) // Compile waits for the scan to finish and compiles all functions. @@ -16,13 +17,43 @@ func Compile(b *build.Build) (*core.Environment, error) { return nil, err } + // Check for existence of `init` + init, exists := all.Functions["core.init"] + + if !exists { + return nil, MissingInitFunction + } + // Check for existence of `main` - _, exists := all.Functions["main.main"] + _, exists = all.Functions["main.main"] if !exists { return nil, MissingMainFunction } + // Resolve types + for _, f := range all.Functions { + f.Type = &types.Function{ + Input: make([]types.Type, len(f.Input)), + Output: make([]types.Type, len(f.Output)), + } + + for i, input := range f.Input { + input.Typ = types.Parse(input.Source[1:], f.File.Bytes) + f.Type.Input[i] = input.Typ + } + + for i, output := range f.Output { + if len(output.Source) > 1 { + output.Typ = types.Parse(output.Source[1:], f.File.Bytes) + } else { + output.Typ = types.Parse(output.Source, f.File.Bytes) + } + + f.Type.Output[i] = output.Typ + } + } + compileFunctions(maps.Values(all.Functions)) for _, f := range all.Functions { @@ -32,7 +63,7 @@ func Compile(b *build.Build) (*core.Environment, error) { } if b.ShowSSA { - showSSA(maps.Values(all.Functions)) + showSSA(init) } return all, nil diff --git a/src/compiler/errors.go b/src/compiler/errors.go index f2908a4..79f74c8 100644 --- a/src/compiler/errors.go +++ b/src/compiler/errors.go @@ -3,5 +3,6 @@ package compiler import "git.urbach.dev/cli/q/src/errors" var ( + MissingInitFunction = errors.String("Missing init function") MissingMainFunction = errors.String("Missing main function") ) \ No newline at end of file diff --git a/src/compiler/showSSA.go b/src/compiler/showSSA.go index ea4984e..4b0bf92 100644 --- a/src/compiler/showSSA.go +++ b/src/compiler/showSSA.go @@ -2,16 +2,15 @@ package compiler import ( "fmt" - "iter" "git.urbach.dev/cli/q/src/core" "git.urbach.dev/go/color/ansi" ) // showSSA shows the SSA IR. -func showSSA(functions iter.Seq[*core.Function]) { - for f := range functions { - ansi.Bold.Printf("%s:\n", f.UniqueName) +func showSSA(root *core.Function) { + root.EachDependency(make(map[*core.Function]bool), func(f *core.Function) { + ansi.Yellow.Printf("%s:\n", f.UniqueName) for i, block := range f.Blocks { if i != 0 { @@ -19,8 +18,13 @@ func showSSA(functions iter.Seq[*core.Function]) { } for i, instr := range block.Instructions { - fmt.Printf("t%d = %s\n", i, instr.String()) + ansi.Dim.Printf("%-4d", i) + fmt.Printf("%-40s", instr.String()) + ansi.Cyan.Printf("%-30s", instr.Type().Name()) + ansi.Dim.Printf("%s\n", f.File.Bytes[instr.Start():instr.End()]) } } - } + + fmt.Println() + }) } \ No newline at end of file diff --git a/src/core/CheckDeadCode.go b/src/core/CheckDeadCode.go index c0d5274..6b31f64 100644 --- a/src/core/CheckDeadCode.go +++ b/src/core/CheckDeadCode.go @@ -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()) } } diff --git a/src/core/Compile.go b/src/core/Compile.go index 3d14db1..6f8587f 100644 --- a/src/core/Compile.go +++ b/src/core/Compile.go @@ -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: diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 9ededcc..4ef5ac6 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -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 diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 9073f41..47dca6c 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -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 } diff --git a/src/core/Function.go b/src/core/Function.go index 15c55b4..2270649 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -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 } diff --git a/src/core/Function_test.go b/src/core/Function_test.go index 42efe79..65d90db 100644 --- a/src/core/Function_test.go +++ b/src/core/Function_test.go @@ -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() } \ No newline at end of file diff --git a/src/core/Parameter.go b/src/core/Parameter.go deleted file mode 100644 index 87d2dae..0000000 --- a/src/core/Parameter.go +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/core/errors.go b/src/core/errors.go index e8fb774..a1257fb 100644 --- a/src/core/errors.go +++ b/src/core/errors.go @@ -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 diff --git a/src/expression/Expression.go b/src/expression/Expression.go index 3450120..a44e065 100644 --- a/src/expression/Expression.go +++ b/src/expression/Expression.go @@ -11,6 +11,7 @@ type Expression struct { Parent *Expression Children []*Expression Token token.Token + Source token.List precedence int8 } diff --git a/src/expression/Parse.go b/src/expression/Parse.go index 110096c..5e57e17 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -7,12 +7,13 @@ import ( ) // Parse generates an expression tree from tokens. -func Parse(tokens []token.Token) *Expression { +func Parse(tokens token.List) *Expression { var ( cursor *Expression root *Expression groupLevel = 0 groupPosition = 0 + cursorStart = 0 ) for i, t := range tokens { @@ -38,6 +39,7 @@ func Parse(tokens []token.Token) *Expression { parameters := NewList(tokens[groupPosition:i]) node := New() node.Token.Position = tokens[groupPosition].Position + node.Source = tokens[groupPosition:i] switch t.Kind { case token.GroupEnd: @@ -72,6 +74,7 @@ func Parse(tokens []token.Token) *Expression { continue } + group.Source = tokens[groupPosition:i] group.precedence = math.MaxInt8 if cursor == nil { @@ -79,6 +82,7 @@ func Parse(tokens []token.Token) *Expression { cursor = New() cursor.Token.Position = tokens[groupPosition].Position cursor.Token.Kind = token.Array + cursor.Source = tokens[groupPosition:i] cursor.precedence = precedence(token.Array) cursor.AddChild(group) root = cursor @@ -88,6 +92,7 @@ func Parse(tokens []token.Token) *Expression { } } else { cursor.AddChild(group) + cursor.Source = tokens[cursorStart : i+1] } continue @@ -100,9 +105,13 @@ func Parse(tokens []token.Token) *Expression { if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String || t.Kind == token.Rune { if cursor != nil { node := NewLeaf(t) + node.Source = tokens[i : i+1] + cursor.Source = tokens[cursorStart : i+1] cursor.AddChild(node) } else { cursor = NewLeaf(t) + cursorStart = i + cursor.Source = tokens[i : i+1] root = cursor } @@ -115,12 +124,15 @@ func Parse(tokens []token.Token) *Expression { if cursor == nil { cursor = NewLeaf(t) + cursorStart = i + cursor.Source = tokens[i : i+1] cursor.precedence = precedence(t.Kind) root = cursor continue } node := NewLeaf(t) + node.Source = tokens[i : i+1] node.precedence = precedence(t.Kind) if cursor.Token.IsOperator() { @@ -131,6 +143,7 @@ func Parse(tokens []token.Token) *Expression { if len(cursor.Children) == numOperands(cursor.Token.Kind) { cursor.LastChild().InsertAbove(node) } else { + cursor.Source = tokens[cursorStart : i+1] cursor.AddChild(node) } } else { @@ -169,5 +182,9 @@ func Parse(tokens []token.Token) *Expression { cursor = node } + if root != nil { + root.Source = tokens + } + return root } \ No newline at end of file diff --git a/src/scanner/scanSignature.go b/src/scanner/scanSignature.go index 0719641..518a654 100644 --- a/src/scanner/scanSignature.go +++ b/src/scanner/scanSignature.go @@ -4,6 +4,7 @@ import ( "git.urbach.dev/cli/q/src/core" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/ssa" "git.urbach.dev/cli/q/src/token" ) @@ -94,9 +95,9 @@ func scanSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind return nil, i, errors.New(MissingType, file, param[0].End()) } - function.Input = append(function.Input, &core.Parameter{ - Name: param[0].String(file.Bytes), - TypeTokens: param[1:], + function.Input = append(function.Input, &ssa.Parameter{ + Name: param[0].String(file.Bytes), + Source: ssa.Source(param), }) } @@ -127,18 +128,19 @@ func scanSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind } if len(param) == 1 { - function.Output = append(function.Output, &core.Parameter{ - Name: "", - TypeTokens: param, + function.Output = append(function.Output, &ssa.Parameter{ + Name: "", + Source: ssa.Source(param), }) } else { - function.Output = append(function.Output, &core.Parameter{ - Name: param[0].String(file.Bytes), - TypeTokens: param[1:], + function.Output = append(function.Output, &ssa.Parameter{ + Name: param[0].String(file.Bytes), + Source: ssa.Source(param), }) } errorPos = param[len(param)-1].End() + 1 } + return function, i, nil } \ No newline at end of file diff --git a/src/ssa/Arguments.go b/src/ssa/Arguments.go index 7b84610..0cff931 100644 --- a/src/ssa/Arguments.go +++ b/src/ssa/Arguments.go @@ -1,20 +1,18 @@ package ssa -type Arguments struct { - Args []Value -} +type Arguments []Value -func (v *Arguments) Dependencies() []Value { - return v.Args +func (v Arguments) Dependencies() []Value { + return v } func (a Arguments) Equals(b Arguments) bool { - if len(a.Args) != len(b.Args) { + if len(a) != len(b) { return false } - for i := range a.Args { - if !a.Args[i].Equals(b.Args[i]) { + for i := range a { + if !a[i].Equals(b[i]) { return false } } diff --git a/src/ssa/BinaryOperation.go b/src/ssa/BinaryOperation.go index 44ef9e8..bdeae32 100644 --- a/src/ssa/BinaryOperation.go +++ b/src/ssa/BinaryOperation.go @@ -5,6 +5,7 @@ import ( "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) type BinaryOperation struct { @@ -12,7 +13,7 @@ type BinaryOperation struct { Right Value Op token.Kind Liveness - HasToken + Source } func (v *BinaryOperation) Dependencies() []Value { @@ -26,10 +27,6 @@ func (a *BinaryOperation) Equals(v Value) bool { return false } - if a.Source.Kind != b.Source.Kind { - return false - } - if !a.Left.Equals(b.Left) { return false } @@ -47,4 +44,8 @@ func (v *BinaryOperation) IsConst() bool { func (v *BinaryOperation) String() string { return fmt.Sprintf("%s %s %s", v.Left, expression.Operators[v.Op].Symbol, v.Right) +} + +func (v *BinaryOperation) Type() types.Type { + return v.Left.Type() } \ No newline at end of file diff --git a/src/ssa/Bytes.go b/src/ssa/Bytes.go index 639bb17..4c1eedd 100644 --- a/src/ssa/Bytes.go +++ b/src/ssa/Bytes.go @@ -1,11 +1,16 @@ package ssa -import "bytes" +import ( + "bytes" + "strconv" + + "git.urbach.dev/cli/q/src/types" +) type Bytes struct { Bytes []byte Liveness - HasToken + Source } func (v *Bytes) Dependencies() []Value { @@ -27,5 +32,9 @@ func (v *Bytes) IsConst() bool { } func (v *Bytes) String() string { - return string(v.Bytes) + return strconv.Quote(string(v.Bytes)) +} + +func (v *Bytes) Type() types.Type { + return types.String } \ No newline at end of file diff --git a/src/ssa/Call.go b/src/ssa/Call.go index cfcd423..717891d 100644 --- a/src/ssa/Call.go +++ b/src/ssa/Call.go @@ -1,11 +1,15 @@ package ssa -import "fmt" +import ( + "fmt" + + "git.urbach.dev/cli/q/src/types" +) type Call struct { Arguments Liveness - HasToken + Source } func (a *Call) Equals(v Value) bool { @@ -23,5 +27,15 @@ func (v *Call) IsConst() bool { } func (v *Call) String() string { - return fmt.Sprintf("call%v", v.Args) + return fmt.Sprintf("%s(%v)", v.Arguments[0], v.Arguments[1:]) +} + +func (v *Call) Type() types.Type { + typ := v.Arguments[0].(*Function).Typ + + if len(typ.Output) == 0 { + return types.Void + } + + return typ.Output[0] } \ No newline at end of file diff --git a/src/ssa/Function.go b/src/ssa/Function.go index c49cf29..8089e55 100644 --- a/src/ssa/Function.go +++ b/src/ssa/Function.go @@ -1,9 +1,12 @@ package ssa +import "git.urbach.dev/cli/q/src/types" + type Function struct { UniqueName string + Typ *types.Function Liveness - HasToken + Source } func (v *Function) Dependencies() []Value { @@ -26,4 +29,8 @@ func (v *Function) IsConst() bool { func (v *Function) String() string { return v.UniqueName +} + +func (v *Function) Type() types.Type { + return v.Typ } \ No newline at end of file diff --git a/src/ssa/HasToken.go b/src/ssa/HasToken.go deleted file mode 100644 index e6c45b9..0000000 --- a/src/ssa/HasToken.go +++ /dev/null @@ -1,11 +0,0 @@ -package ssa - -import "git.urbach.dev/cli/q/src/token" - -type HasToken struct { - Source token.Token -} - -func (v *HasToken) Token() token.Token { - return v.Source -} \ No newline at end of file diff --git a/src/ssa/IR.go b/src/ssa/IR.go index 8a7cb6b..b23ddc9 100644 --- a/src/ssa/IR.go +++ b/src/ssa/IR.go @@ -1,5 +1,7 @@ package ssa +import "git.urbach.dev/cli/q/src/types" + // IR is a list of basic blocks. type IR struct { Blocks []*Block @@ -39,16 +41,9 @@ func (f *IR) AppendInt(x int) *Int { return v } -// AppendRegister adds a new register value to the last block. -func (f *IR) AppendRegister(index int) *Parameter { - v := &Parameter{Index: uint8(index)} - f.Append(v) - return v -} - // AppendFunction adds a new function value to the last block. -func (f *IR) AppendFunction(name string) *Function { - v := &Function{UniqueName: name} +func (f *IR) AppendFunction(name string, typ *types.Function) *Function { + v := &Function{UniqueName: name, Typ: typ} f.Append(v) return v } diff --git a/src/ssa/Int.go b/src/ssa/Int.go index 9ff9256..7f6f9fc 100644 --- a/src/ssa/Int.go +++ b/src/ssa/Int.go @@ -2,12 +2,14 @@ package ssa import ( "fmt" + + "git.urbach.dev/cli/q/src/types" ) type Int struct { Int int Liveness - HasToken + Source } func (v *Int) Dependencies() []Value { @@ -30,4 +32,8 @@ func (v *Int) IsConst() bool { func (v *Int) String() string { return fmt.Sprintf("%d", v.Int) +} + +func (v *Int) Type() types.Type { + return types.AnyInt } \ No newline at end of file diff --git a/src/ssa/Parameter.go b/src/ssa/Parameter.go index e5f8cb0..93e25b9 100644 --- a/src/ssa/Parameter.go +++ b/src/ssa/Parameter.go @@ -1,11 +1,17 @@ package ssa -import "fmt" +import ( + "fmt" + + "git.urbach.dev/cli/q/src/types" +) type Parameter struct { Index uint8 + Name string + Typ types.Type Liveness - HasToken + Source } func (v *Parameter) Dependencies() []Value { @@ -27,5 +33,9 @@ func (v *Parameter) IsConst() bool { } func (v *Parameter) String() string { - return fmt.Sprintf("arg[%d]", v.Index) + return fmt.Sprintf("in[%d]", v.Index) +} + +func (v *Parameter) Type() types.Type { + return v.Typ } \ No newline at end of file diff --git a/src/ssa/Return.go b/src/ssa/Return.go index 9472f23..72a6010 100644 --- a/src/ssa/Return.go +++ b/src/ssa/Return.go @@ -1,10 +1,14 @@ package ssa -import "fmt" +import ( + "fmt" + + "git.urbach.dev/cli/q/src/types" +) type Return struct { Arguments - HasToken + Source } func (a *Return) AddUse(user Value) { panic("return is not a value") } @@ -17,12 +21,12 @@ func (a *Return) Equals(v Value) bool { return false } - if len(a.Args) != len(b.Args) { + if len(a.Arguments) != len(b.Arguments) { return false } - for i := range a.Args { - if !a.Args[i].Equals(b.Args[i]) { + for i := range a.Arguments { + if !a.Arguments[i].Equals(b.Arguments[i]) { return false } } @@ -35,5 +39,9 @@ func (v *Return) IsConst() bool { } func (v *Return) String() string { - return fmt.Sprintf("return %v", v.Args) + return fmt.Sprintf("return %v", v.Arguments) +} + +func (v *Return) Type() types.Type { + return types.Void } \ No newline at end of file diff --git a/src/ssa/Source.go b/src/ssa/Source.go new file mode 100644 index 0000000..28330f2 --- /dev/null +++ b/src/ssa/Source.go @@ -0,0 +1,13 @@ +package ssa + +import "git.urbach.dev/cli/q/src/token" + +type Source token.List + +func (v Source) Start() token.Position { + return v[0].Position +} + +func (v Source) End() token.Position { + return v[len(v)-1].End() +} \ No newline at end of file diff --git a/src/ssa/Syscall.go b/src/ssa/Syscall.go index 525b6bc..7847c78 100644 --- a/src/ssa/Syscall.go +++ b/src/ssa/Syscall.go @@ -1,11 +1,15 @@ package ssa -import "fmt" +import ( + "fmt" + + "git.urbach.dev/cli/q/src/types" +) type Syscall struct { Arguments Liveness - HasToken + Source } func (a *Syscall) Equals(v Value) bool { @@ -23,5 +27,9 @@ func (v *Syscall) IsConst() bool { } func (v *Syscall) String() string { - return fmt.Sprintf("syscall%v", v.Args) + return fmt.Sprintf("syscall(%v)", v.Arguments) +} + +func (v *Syscall) Type() types.Type { + return types.Any } \ No newline at end of file diff --git a/src/ssa/Value.go b/src/ssa/Value.go index 0cfacbf..db718b2 100644 --- a/src/ssa/Value.go +++ b/src/ssa/Value.go @@ -1,13 +1,18 @@ package ssa -import "git.urbach.dev/cli/q/src/token" +import ( + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" +) type Value interface { AddUse(Value) Alive() int Dependencies() []Value + End() token.Position Equals(Value) bool IsConst() bool String() string - Token() token.Token + Start() token.Position + Type() types.Type } \ No newline at end of file diff --git a/src/types/Common.go b/src/types/Common.go index 6af5a2f..07796b9 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -17,6 +17,7 @@ var ( UInt32 = &Base{name: "uint32", size: 4} UInt16 = &Base{name: "uint16", size: 2} UInt8 = &Base{name: "uint8", size: 1} + Void = &Base{name: "void", size: 0} ) var ( diff --git a/src/types/Function.go b/src/types/Function.go new file mode 100644 index 0000000..855e66b --- /dev/null +++ b/src/types/Function.go @@ -0,0 +1,47 @@ +package types + +import "strings" + +// Function transforms inputs to new outputs. +type Function struct { + Input []Type + Output []Type +} + +// Name returns the type name. +func (f *Function) Name() string { + builder := strings.Builder{} + builder.WriteString("(") + + for i, input := range f.Input { + builder.WriteString(input.Name()) + + if i != len(f.Input)-1 { + builder.WriteString(", ") + } + } + + builder.WriteString(")") + + if len(f.Output) == 0 { + return builder.String() + } + + builder.WriteString(" -> (") + + for i, output := range f.Output { + builder.WriteString(output.Name()) + + if i != len(f.Output)-1 { + builder.WriteString(",") + } + } + + builder.WriteString(")") + return builder.String() +} + +// Size returns the total size in bytes. +func (f *Function) Size() int { + return 8 +} \ No newline at end of file diff --git a/src/types/Parse.go b/src/types/Parse.go new file mode 100644 index 0000000..1958c1d --- /dev/null +++ b/src/types/Parse.go @@ -0,0 +1,71 @@ +package types + +import ( + "git.urbach.dev/cli/q/src/token" +) + +// Parse returns the type with the given tokens or `nil` if it doesn't exist. +func Parse[T ~[]token.Token](tokens T, source []byte) Type { + if tokens[0].Kind == token.Mul { + to := tokens[1:] + typ := Parse(to, source) + + if typ == Any { + return AnyPointer + } + + return &Pointer{To: typ} + } + + if len(tokens) >= 2 && tokens[0].Kind == token.ArrayStart && tokens[1].Kind == token.ArrayEnd { + to := tokens[2:] + typ := Parse(to, source) + + if typ == Any { + return AnyArray + } + + return &Array{Of: typ} + } + + if tokens[0].Kind != token.Identifier { + return nil + } + + switch tokens[0].String(source) { + case "int": + return Int + case "int64": + return Int64 + case "int32": + return Int32 + case "int16": + return Int16 + case "int8": + return Int8 + case "uint": + return UInt + case "uint64": + return UInt64 + case "uint32": + return UInt32 + case "uint16": + return UInt16 + case "uint8": + return UInt8 + case "byte": + return Byte + case "bool": + return Bool + case "float": + return Float + case "float64": + return Float64 + case "float32": + return Float32 + case "any": + return Any + default: + return nil + } +} \ No newline at end of file