Add type generator

This commit is contained in:
Sasha Koshka 2023-11-16 22:04:56 -05:00
parent e501577555
commit 3a1d9e20c2
5 changed files with 301 additions and 6 deletions

View File

@ -1,11 +1,34 @@
package generator
import "io"
import "fmt"
import "sort"
import "errors"
import "github.com/llir/llvm/ir"
import "github.com/llir/llvm/ir/types"
import "git.tebibyte.media/sashakoshka/fspl/analyzer"
// Target contains information about the machine the code is being written for.
type Target struct {
// WordSize is the size of the machine word. This determines the width
// of the Word type.
WordSize uint64
// Arch specifies the machine architecture
Arch string
}
// NativeTarget returns a target describing the current system.
func NativeTarget () Target {
// FIXME
return Target {
WordSize: 64,
Arch: "x86",
}
}
type generator struct {
target Target
tree analyzer.Tree
output io.Writer
module ir.Module
@ -14,26 +37,41 @@ type generator struct {
// Generate takes in a semantic tree and writes corresponding LLVM IR to the
// given io.Writer. It returns an error in case there is something wrong with
// the semantic tree that prevents the code generation process from occurring.
func Generate (tree analyzer.Tree, output io.Writer) error {
func (this Target) Generate (tree analyzer.Tree, output io.Writer) error {
return (&generator {
target: this,
tree: tree,
output: output,
}).generate()
}
func (this *generator) generate () error {
for name, ty := range this.tree.Types {
typedefs := sortMapKeys(this.tree.Types)
for _, key := range typedefs {
_, err := this.typedef(key)
if err != nil { return err }
}
// TODO functions
// TODO methods
_, err := this.module.WriteTo(this.output)
return err
}
func (this *generator) ty (typeName string) types.Type {
func (this *generator) typedef (typeName string) (types.Type, error) {
for _, ty := range this.module.TypeDefs {
if ty.Name() == typeName {
return ty
return ty, nil
}
}
return nil
def, exists := this.tree.Types[typeName]
if !exists {
return nil,
errors.New(fmt.Sprintln("type", typeName, "not found"))
}
return this.generateTypedef(def)
}
func (this *generator) method (typeName string, name string) *ir.Func {
@ -53,3 +91,14 @@ func (this *generator) function (name string) *ir.Func {
}
return nil
}
func sortMapKeys[T any] (unsorted map[string] T) []string {
keys := make([]string, len(unsorted))
index := 0
for key := range unsorted {
keys[index] = key
index ++
}
sort.Strings(keys)
return keys
}

45
generator/test-common.go Normal file
View File

@ -0,0 +1,45 @@
package generator
import "io"
import "fmt"
import "testing"
import "strings"
import "git.tebibyte.media/sashakoshka/fspl/parser"
import "git.tebibyte.media/sashakoshka/fspl/analyzer"
func testString (test *testing.T, correct string, input string) {
testReader(test, correct, strings.NewReader(input))
}
func testReader (test *testing.T, correct string, inputs ...io.Reader) {
ast := parser.Tree { }
for index, stream := range inputs {
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
if err != nil && err != io.EOF{
test.Error("parser returned error: ", err)
return
}
}
tree := analyzer.Tree { }
err := tree.Analyze(ast)
if err != nil {
test.Error("analyzer returned error: ", err)
return
}
output := new(strings.Builder)
err = NativeTarget().Generate(tree, output)
if err != nil {
test.Error("generator returned error: ", err)
return
}
got := output.String()
if got != correct {
test.Logf("results do not match")
test.Logf("got:\n%v", got)
test.Logf("correct:\n%v", correct)
test.Fail()
}
}

View File

@ -0,0 +1,43 @@
package generator
import "fmt"
import "github.com/llir/llvm/ir/types"
import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *generator) generateTypedef (
def *entity.Typedef,
) (
types.Type,
error,
) {
irType, err := this.generateType(def.Type)
if err != nil { return nil, err }
return this.module.NewTypeDef(def.Name, irType), nil
}
func (this *generator) generateType (ty entity.Type) (types.Type, error) {
switch ty.(type) {
case nil:
return types.Void, nil
case *entity.TypeNamed:
return this.generateTypeNamed(ty.(*entity.TypeNamed))
case *entity.TypePointer:
return this.generateTypePointer(ty.(*entity.TypePointer))
case *entity.TypeSlice:
return this.generateTypeSlice(ty.(*entity.TypeSlice))
case *entity.TypeArray:
return this.generateTypeArray(ty.(*entity.TypeArray))
case *entity.TypeStruct:
return this.generateTypeStruct(ty.(*entity.TypeStruct))
case *entity.TypeInterface:
return this.generateTypeInerface(ty.(*entity.TypeInterface))
case *entity.TypeInt:
return this.generateTypeInt(ty.(*entity.TypeInt))
case *entity.TypeFloat:
return this.generateTypeFloat(ty.(*entity.TypeFloat))
case *entity.TypeWord:
return this.generateTypeWord(ty.(*entity.TypeWord))
default:
panic(fmt.Sprintln("generator doesn't know about type", ty))
}
}

106
generator/type.go Normal file
View File

@ -0,0 +1,106 @@
package generator
import "fmt"
import "errors"
import "github.com/llir/llvm/ir/types"
import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *generator) generateTypeNamed (ty *entity.TypeNamed) (types.Type, error) {
return this.typedef(ty.Name)
}
func (this *generator) generateTypePointer (ty *entity.TypePointer) (types.Type, error) {
return types.NewPointer(&types.VoidType { }), nil
}
func (this *generator) generateTypeSlice (ty *entity.TypeSlice) (types.Type, error) {
return types.NewStruct (
/* data */ types.NewPointer(types.Void),
/* len */ types.NewInt(this.target.WordSize)), nil
}
func (this *generator) generateTypeArray (ty *entity.TypeArray) (types.Type, error) {
element, err := this.generateType(ty.Element)
if err != nil { return nil, err }
return types.NewArray(uint64(ty.Length), element), nil
}
func (this *generator) generateTypeStruct (ty *entity.TypeStruct) (types.Type, error) {
irStruct := &types.StructType {
Fields: make([]types.Type, len(ty.Members)),
}
for index, member := range ty.Members {
field, err := this.generateType(member.Type())
if err != nil { return nil, err }
irStruct.Fields[index] = field
}
return irStruct, nil
}
func (this *generator) generateTypeInerface (ty *entity.TypeInterface) (types.Type, error) {
irStruct := &types.StructType {
Fields: make([]types.Type, len(ty.Behaviors) + 1),
}
// object pointer
irStruct.Fields[0] = types.NewPointer(types.Void)
// behaviors
for index, behavior := range ty.Behaviors {
field, err := this.generateTypeFunction(behavior)
if err != nil { return nil, err }
irStruct.Fields[index + 1] = field
}
return irStruct, nil
}
func (this *generator) generateTypeInt (ty *entity.TypeInt) (types.Type, error) {
return types.NewInt(uint64(ty.Width)), nil
}
func (this *generator) generateTypeFloat (ty *entity.TypeFloat) (types.Type, error) {
var kind types.FloatKind; switch ty.Width {
case 16: kind = types.FloatKindHalf
case 32: kind = types.FloatKindFloat
case 64: kind = types.FloatKindDouble
case 80:
if this.target.Arch == "x86" {
kind = types.FloatKindX86_FP80
} else {
return nil, errors.New(fmt.Sprintln (
ty, "not available on",
this.target.Arch))
}
case 128:
if this.target.Arch == "PowerPC" {
kind = types.FloatKindPPC_FP128
} else {
kind = types.FloatKindFP128
}
default:
return nil, errors.New(fmt.Sprintln (ty, "has invalid width"))
}
return &types.FloatType { Kind: kind }, nil
}
func (this *generator) generateTypeWord (ty *entity.TypeWord) (types.Type, error) {
return types.NewInt(this.target.WordSize), nil
}
func (this *generator) generateTypeFunction (
signature *entity.Signature,
) (
types.Type,
error,
) {
irFunc := &types.FuncType {
Params: make([]types.Type, len(signature.Arguments)),
}
ret, err := this.generateType(signature.Return)
if err != nil { return nil, err }
irFunc.RetType = ret
for index, argument := range signature.Arguments {
param, err := this.generateType(argument.Type())
if err != nil { return nil, err }
irFunc.Params[index] = param
}
return irFunc, nil
}

52
generator/type_test.go Normal file
View File

@ -0,0 +1,52 @@
package generator
import "testing"
func TestType (test *testing.T) {
testString (test,
`%AllFloats = type { float, double }
%Bool = type i8
%Byte = type i8
%Index = type i64
%Rune = type i32
%AllInts = type { %Bool, %Byte, %Index, %Rune, i64, i64, i8, i16, i32, i64, i8, i16, i32, i64 }
%Path = type { void*, i64 }
%Pegasus = type { void*, void ({ void*, i64 }), void ({ void*, i64 }), void (i64), %Bool () }
%Point = type { i64, i64 }
%Quadrangle = type [4 x %Point]
%Rectangle = type { %Point, %Point }
%String = type { void*, i64 }
`,
`
Point: (x:Int y:Int)
Pegasus: (
[clear clouds:*:Point]
[fly rings:*:Point]
[fall distance:Int]
[complete]:Bool)
Rectangle: (
min:Point
max:Point)
AllInts: (
bool:Bool
byte:Byte
index:Index
rune:Rune
word:Int
uword:UInt
i8:I8
i16:I16
i32:I32
i64:I64
u8:U8
u16:U16
u32:U32
u64:U64
)
AllFloats: (
f32:F32
f64:F64)
Path: *:Point
Quadrangle: 4:Point
`)
}