I would like some reviews of my Type system library in Golang that I am building for a compiler project.
Here are external links to the versioned source files,
The first file:
// types.go
package virt
import (
"fmt"
"reflect"
"strings"
)
// The Type interface is responsible for providing a uniform base for
// all subsequent types of the type system
type Type interface {
// Size is used to get the size (in bytes) of the given type
Size() int
// TypeString returns the bare type representation
TypeString() string
// Kind returns the kind of the type (ie. Unit, Pointer, ...)
Kind() TypeKind
// Id returns the ID of the given type. A type's ID is bound to the underlying
// type(s) rather than the type itself. This means that a type alias and its
// source share the same TypeID.
// This also implies that two structs that have the same memory layout share
// the same TypeID.
Id() TypeID
// Equals determines whether two given types are the same or not.
// Two types are equal should their TypeString be the same
Equals(Type) bool
fmt.Stringer
}
// UnitType represents the absence of type (typeless)
type UnitType struct{}
// UndefinedType represents the type of undefined values/symbols
type UndefinedType struct{}
// TypeAlias is simply used to create type aliases
type TypeAlias struct {
// The name of the alias
Name string
// The aliased type
Of Type
}
// BaseType represents primitive types such comp integers and floats
type BaseType struct {
Alias string
// Which base type (hear primitive) is concerned
Which TypeID
}
// PointerType represents all types that are pointers to other types
type PointerType struct {
Alias string
// The Type pointed to by the PointerType
To Type
}
// ArrayType represents all types of fixed-length
type ArrayType struct {
Alias string
ElemCount int
// The type of the elements held by such an array
Of Type
}
// A StructType represents struct-like data structure
type StructType struct {
Alias string
// The Fields listed in the struct type
Fields []Type
}
// An EnumType represents type enumerations
type EnumType struct {
Alias string
// The Entries (named) of the enum type
Entries map[string]struct{}
}
// A FunctionType holds the signature of a function
type FunctionType struct {
Alias string
// The return type of a function type
Returns Type
// The arguments of a function type
Args []Type
}
// Compile-time check
var (
_ Type = (*UnitType)(nil)
_ Type = (*UndefinedType)(nil)
_ Type = (*TypeAlias)(nil)
_ Type = (*BaseType)(nil)
_ Type = (*PointerType)(nil)
_ Type = (*ArrayType)(nil)
_ Type = (*StructType)(nil)
_ Type = (*EnumType)(nil)
_ Type = (*FunctionType)(nil)
)
// TypeKind represents the type class of a type
type TypeKind = int
const (
UnitKind = iota
UndefinedKind
BasicKind
PointerKind
ArrayKind
StructKind
EnumKind
FunctionKind
)
// TypeID is used to hold hashes of types to identify them
type TypeID = uint64
// hash hashes a string using the java.lang.String.hashCode method
func hash(str string) TypeID {
var hash TypeID = 7
for _, c := range str {
hash = hash*31 + TypeID(c)
}
return hash
}
// These values are used to identify builtin types
const (
u8typeID TypeID = iota
u16typeID
u32typeID
u64typeID
i8typeID
i16typeID
i32typeID
i64typeID
f64typeID
f32typeID
)
const (
unitTypeString = "unit"
undefTypeString = "undef"
funcTypeArrow = "->"
nilTypeReplacement = "%!(nil)"
)
/*** UnitType implementation ***/
// Size implements Type
func (t *UnitType) Size() int {
return 0
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *UnitType) TypeString() string {
return unitTypeString
}
// Kind implements Type
func (t *UnitType) Kind() TypeKind {
return UnitKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *UnitType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *UnitType) Equals(other Type) bool {
return other != nil && other.Kind() == UnitKind
}
// String implements fmt.Stringer
func (t *UnitType) String() string {
return unitTypeString
}
/*** UndefinedType implementation ***/
// Size implements Type
func (t *UndefinedType) Size() int {
return 0
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *UndefinedType) TypeString() string {
return undefTypeString
}
// Kind implements Type
func (t *UndefinedType) Kind() TypeKind {
return UndefinedKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *UndefinedType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *UndefinedType) Equals(other Type) bool {
return other != nil && other.Kind() == UndefinedKind
}
// String implements fmt.Stringer
func (t *UndefinedType) String() string {
return undefTypeString
}
/*** TypeAlias implementation ***/
// Size implements Type
func (t *TypeAlias) Size() int {
if t.Of == nil {
return 0
}
return t.Of.Size()
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *TypeAlias) TypeString() string {
switch t.Name {
case "":
if t.Of == nil {
return nilTypeReplacement
}
return t.Of.String()
default:
return t.Name
}
}
// Kind implements Type
func (t *TypeAlias) Kind() TypeKind {
if t.Of == nil {
return -1
}
return t.Of.Kind()
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *TypeAlias) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *TypeAlias) Equals(other Type) bool {
if other == nil || t.Of == nil {
return false
}
return t.Of.Equals(other)
}
// String implements fmt.Stringer
func (t *TypeAlias) String() string {
return t.TypeString()
}
/*** BaseType implementation ***/
// Size implements Type
func (t *BaseType) Size() int {
switch t.Which {
case i8typeID, u8typeID:
return 1
case i16typeID, u16typeID:
return 2
case i32typeID, u32typeID, f32typeID:
return 4
case i64typeID, u64typeID, f64typeID:
return 8
}
return 0
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *BaseType) TypeString() string {
return fmt.Sprintf("primitive<%d>", t.Which)
}
// Kind implements Type
func (t *BaseType) Kind() TypeKind {
return BasicKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *BaseType) Id() TypeID {
return t.Which
}
// Equals implements Type
func (t *BaseType) Equals(other Type) bool {
if other == nil || other.Kind() != BasicKind {
return false
}
return t.Which == other.(*BaseType).Which
}
// String implements fmt.Stringer
func (t *BaseType) String() string {
if t.Alias != "" {
return t.Alias
}
return t.TypeString()
}
/*** PointerType implementation ***/
// Size implements Type
func (t *PointerType) Size() int {
return Unsigned64.Size()
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *PointerType) TypeString() string {
if t.To == nil {
return nilTypeReplacement + "&"
}
return t.To.String() + "&"
}
// Kind implements Type
func (t *PointerType) Kind() TypeKind {
return PointerKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *PointerType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *PointerType) Equals(other Type) bool {
if other == nil || other.Kind() != PointerKind {
return false
}
return t.To.Equals(other.(*PointerType).To)
}
// String implements fmt.Stringer
func (t *PointerType) String() string {
if t.Alias != "" {
return t.Alias
}
return t.TypeString()
}
/*** ArrayType implementation ***/
// Size implements Type
func (t *ArrayType) Size() int {
if t.Of == nil {
return 0
}
return t.ElemCount * t.Of.Size()
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *ArrayType) TypeString() string {
if t.Of == nil {
return fmt.Sprintf("%s[%d]", nilTypeReplacement, t.ElemCount)
}
return fmt.Sprintf("%s[%d]", t.Of.String(), t.ElemCount)
}
// Kind implements Type
func (t *ArrayType) Kind() TypeKind {
return ArrayKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *ArrayType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *ArrayType) Equals(other Type) bool {
if other == nil || other.Kind() != ArrayKind {
return false
}
return t.Id() == other.Id()
}
// String implements fmt.Stringer
func (t *ArrayType) String() string {
if t.Alias != "" {
return t.Alias
}
return t.TypeString()
}
/*** StructType implementation ***/
// Size implements Type
func (t *StructType) Size() int {
if t.Fields == nil {
return 0
}
var size int
for _, typ := range t.Fields {
if typ != nil {
size += typ.Size()
}
}
return size
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *StructType) TypeString() (repr string) {
if t.Fields == nil || len(t.Fields) == 0 {
return "struct{}"
}
repr += "struct {\n "
for _, typ := range t.Fields {
if typ == nil {
repr += nilTypeReplacement
} else {
repr += typ.String()
}
repr += "\n "
}
// Remove the leading spaces on the last type line
repr = repr[:len(repr)-2]
return repr + "}"
}
// Kind implements Type
func (t *StructType) Kind() TypeKind {
return StructKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *StructType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *StructType) Equals(other Type) bool {
if other == nil || other.Kind() != StructKind {
return false
}
return t.Id() == other.Id()
}
// String implements fmt.Stringer
func (t *StructType) String() string {
if t.Alias != "" {
return "struct " + t.Alias
}
return t.TypeString()
}
/*** EnumType implementation ***/
// Size implements Type
func (t *EnumType) Size() int {
return Int.Size()
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *EnumType) TypeString() (repr string) {
if t.Entries == nil || len(t.Entries) == 0 {
return "enum{}"
}
repr = "enum {\n "
for name := range t.Entries {
repr += fmt.Sprintf("%s\n ", name)
}
// Remove 2 trailing whitespaces
repr = repr[:len(repr)-2]
return repr + "}"
}
// Kind implements Type
func (t *EnumType) Kind() TypeKind {
return EnumKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *EnumType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *EnumType) Equals(other Type) bool {
if other == nil || other.Kind() != EnumKind {
return false
}
// Can't use other.Id() == t.Id()
// for enum type strings may vary over time (maps are unordered)
return reflect.DeepEqual(t.Entries, other.(*EnumType).Entries)
}
// String implements fmt.Stringer
func (t *EnumType) String() string {
if t.Alias != "" {
return "enum " + t.Alias
}
return fmt.Sprintf("enum <anonymous>[%d]", len(t.Entries))
}
/*** FunctionType implementation ***/
// DefaultFunctionReturnValue is used to determine the return type of functions
// that would miss such annotation
var DefaultFunctionReturnValue = Unit
// Size implements Type
func (t *FunctionType) Size() int {
return Unsigned64.Size()
}
// TypeString implements Type. It returns the raw string representation of the type
func (t *FunctionType) TypeString() (repr string) {
if t.Args == nil {
repr = "()"
if t.Returns == nil {
repr += " " + funcTypeArrow + " " + nilTypeReplacement
return repr
} else if len(t.Returns.String()) == 0 {
repr += " " + funcTypeArrow + " " + DefaultFunctionReturnValue.String()
return repr
}
repr += " " + funcTypeArrow + " " + t.Returns.String()
return strings.TrimSpace(repr)
}
repr = "("
for _, typ := range t.Args {
if typ != nil {
repr += typ.String()
} else {
repr += nilTypeReplacement
}
repr += ", "
}
// Cut the trailing coma+space if there were args
if len(t.Args) > 0 {
repr = repr[:len(repr)-2]
}
repr += ") "
if t.Returns != nil {
// Add arrow only if type actually has a type string
if len(t.Returns.String()) > 0 {
repr += funcTypeArrow + " "
}
repr += t.Returns.String()
} else {
repr += funcTypeArrow + " "
// TODO nil check
repr += DefaultFunctionReturnValue.String()
}
return strings.TrimSpace(repr)
}
// Kind implements Type
func (t *FunctionType) Kind() TypeKind {
return FunctionKind
}
// Id implements Type. It returns an ID bound to the raw underlying type
func (t *FunctionType) Id() TypeID {
return hash(t.TypeString())
}
// Equals implements Type
func (t *FunctionType) Equals(other Type) bool {
if other == nil || other.Kind() != FunctionKind {
return false
}
return t.Id() == other.Id()
}
// String implements fmt.Stringer
func (t *FunctionType) String() string {
if t.Alias != "" {
return t.Alias
}
return t.TypeString()
}
and the second one ...
// builtins.go
package virt
var (
// Unit represents the absence of type (functional-inspired)
Unit = &UnitType{}
// Undefined is used to describe symbols which type is not yet known
Undefined = &UndefinedType{}
// Unsigned8 represents 1-byte unsigned integers
Unsigned8 = &BaseType{"u8", u8typeID}
// Byte represents 1-byte unsigned integers used to hold characters
Byte = &TypeAlias{"byte", Unsigned8}
// Unsigned16 represents 2-byte unsigned integers
Unsigned16 = &BaseType{"u16", u16typeID}
// Unsigned32 represents 4-byte unsigned integers
Unsigned32 = &BaseType{"u32", u32typeID}
// UnsignedInt represents 4-byte unsigned integers (platform dependant)
UnsignedInt = &TypeAlias{"uint", Unsigned32}
// Unsigned64 represents 8-byte unsigned integers
Unsigned64 = &BaseType{"u64", u64typeID}
// Signed8 represents 1-byte signed integers
Signed8 = &BaseType{"i8", i8typeID}
// Signed16 represents 2-byte signed integers
Signed16 = &BaseType{"i16", i16typeID}
// Signed32 represents 4-byte signed integers
Signed32 = &BaseType{"i32", i32typeID}
// Int represents 4-byte signed integers (platform dependant)
Int = &TypeAlias{"int", Signed32}
// Signed64 is an 8-byte signed integer
Signed64 = &BaseType{"i64", i64typeID}
// Float64 represents 8-byte floating point numbers
Float64 = &BaseType{"f64", f64typeID}
// Float64 represents 4-byte floating point numbers
Float32 = &BaseType{"f32", f32typeID}
// String represents strings of characters (C-like strings)
String = &TypeAlias{"string", &PointerType{"", Byte}}
// Bool represents the usual boolean values through a 1-byte integer
Bool = &TypeAlias{"bool", Unsigned8}
)
The point of this is to help me (or anyone really) build a solid Type System for whatever program might have use for it (mostly compilers or static analysis tools to be honest).
All types must implement the Type interface which should cover anything related to a type really.
Currently implemented type kinds:
- Unit (void)
- Undefined
- Signed / unsigned integers (8, 16, 32, 64)
- Floating point numbers (double, float)
- Pointers (to any other type)
- Arrays (fixed length)
- Structures
- Enums (value less, more as in Java, unlike C)
- Functions (single return value supported yet)
Sorry if this kind of code review is inappropriate, I haven't found a rule / best practices page.
Anyways, thanks for your time!