Merge pull request 'implement-union-types' (#54) from implement-union-types into main

Reviewed-on: #54
This commit is contained in:
2024-03-06 20:16:54 +00:00
33 changed files with 1146 additions and 68 deletions

View File

@@ -90,6 +90,11 @@ func (this *Tree) canAssign (
return this.canAssignInterface(pos, destination, source)
}
// then, check for union assignment
if _, ok := this.isUnion(destination); ok {
return this.canAssignUnion(pos, destination, source)
}
// then, check for array to slice assignment
if destination, ok := ReduceToBase(destination).(*entity.TypeSlice); ok {
if source, ok := ReduceToBase(source). (*entity.TypeArray); ok {
@@ -286,6 +291,27 @@ func (this *Tree) canAssignInterface (
return nil
}
// canAssignUnion takes in an analyzed union destination type and an
// analyzed source type, and determines whether source can be assigned to
// destination.
func (this *Tree) canAssignUnion (
pos errors.Position,
destination entity.Type,
source entity.Type,
) error {
sourceHash := source.Hash()
union, _ := this.isUnion(destination)
// check if types are identical
if entity.TypesEqual(source, destination) { return nil }
// check if type is included within the union
if _, ok := union.AllowedMap[sourceHash]; ok { return nil }
return errors.Errorf (
pos, "%v is not included within union %v",
source, destination)
}
// areStructurallyEquivalent tests whether two types are structurally equivalent
// to eachother.
func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
@@ -343,6 +369,9 @@ func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
// isLocationExpression returns whether or not an expression is a valid location
// expression.
func (this *Tree) isLocationExpression (expression entity.Expression) error {
// TODO: have some Name() method of Expression so we can use it in a
// default case here. this will prevent crashes when new features are
// added.
cannot := func (pos errors.Position, kind string) error {
return errors.Errorf(pos, "cannot assign to %s", kind)
}
@@ -378,6 +407,8 @@ func (this *Tree) isLocationExpression (expression entity.Expression) error {
expression.Source)
case *entity.IfElse:
return cannot(expression.Position, "if/else")
case *entity.Match:
return cannot(expression.Position, "match")
case *entity.Loop:
return cannot(expression.Position, "loop")
case *entity.Break:
@@ -425,6 +456,16 @@ func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) {
}
}
// isUnion takes in an analyzed type and returns true and a union if that type
// refers to a union. If not, it returns nil, false.
func (this *Tree) isUnion (ty entity.Type) (*entity.TypeUnion, bool) {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeUnion: return ty.(*entity.TypeUnion), true
default: return nil, false
}
}
// isNumeric returns whether or not the specified type is a number.
func isNumeric (ty entity.Type) bool {
ty = ReduceToBase(ty)

View File

@@ -249,6 +249,40 @@ BlueJay.[land] = { }
`)
}
func TestAssignmentUnion (test *testing.T) {
testString (test,
`
U: (| Int F64)
[main] = {
x:F64 = 7.7
y:U = x
}
`)
}
func TestAssignmentUnionToUnion (test *testing.T) {
testString (test,
`
U: (| Int F64)
[main] = {
x:U y:U
x = y
}
`)
}
func TestAssignmentUnionErrNotListed (test *testing.T) {
testStringErr (test,
"I64 is not included within union U", 5, 8,
`
U: (| Int F64)
[main] = {
x:I64 = 7
y:U = x
}
`)
}
func TestAssignmentErrByteSliceString (test *testing.T) {
testStringErr (test,
"expected *:Byte", 4, 13,

View File

@@ -44,6 +44,8 @@ func (this *Tree) analyzeExpression (
return this.analyzeMemberAccess(into, mode, expression)
case *entity.IfElse:
return this.analyzeIfElse(into, mode, expression)
case *entity.Match:
return this.analyzeMatch(into, mode, expression)
case *entity.Loop:
return this.analyzeLoop(into, mode, expression)
case *entity.Break:

View File

@@ -216,8 +216,8 @@ func (this *Tree) analyzeSubscript (
) {
slice, err := this.analyzeExpression (
&entity.TypeSlice {
Position: subscript.Position,
Element: into,
Pos: subscript.Position,
Element: into,
}, weak,
subscript.Slice)
if err != nil { return nil, err }
@@ -312,7 +312,7 @@ func (this *Tree) analyzeDereference (
) {
pointer, err := this.analyzeExpression (
&entity.TypePointer {
Position: dereference.Position,
Pos: dereference.Position,
Referenced: into,
}, weak,
dereference.Pointer)
@@ -697,7 +697,7 @@ func (this *Tree) analyzeIfElse (
if into != nil {
return nil, errors.Errorf (
ifelse.Position,
"else case required when using value of if ")
"else case required when using value of if")
}
} else {
falseBranch, err := this.analyzeExpression(into, strict, ifelse.False)
@@ -710,6 +710,85 @@ func (this *Tree) analyzeIfElse (
return ifelse, nil
}
func (this *Tree) analyzeMatch (
into entity.Type,
mode strictness,
match *entity.Match,
) (
entity.Expression,
error,
) {
value, err := this.analyzeExpression(nil, strict, match.Value)
if err != nil { return nil, err }
match.Value = value
rawUnion, ok := this.isUnion(value.Type())
if !ok {
// FIXME: add Position() method to Expression, then change this
// error message (and others) #53
return nil, errors.Errorf (
match.Position, "type %v is not a union",
value.Type())
}
match, err = this.assembleMatchMap(match)
if err != nil { return nil, err }
for index, cas := range match.Cases {
hash := match.CaseOrder[index]
if _, ok := rawUnion.AllowedMap[hash]; !ok {
return nil, errors.Errorf (
cas.Declaration.Ty.Position(),
"%v is not included within %v",
cas.Declaration.Ty, value.Type())
}
this.pushScope(cas)
declaration, err := this.analyzeDeclaration (
nil, strict,
cas.Declaration)
if err != nil { this.popScope(); return nil, err }
cas.Declaration = declaration.(*entity.Declaration)
expression, err := this.analyzeExpression (
into, mode,
cas.Expression)
if err != nil { this.popScope(); return nil, err }
cas.Expression = expression
this.popScope()
match.Cases[index] = cas
match.CaseMap[hash] = cas
}
if into != nil && len(match.Cases) < len(rawUnion.Allowed) {
return nil, errors.Errorf (
match.Position,
"match does not cover all types within %v",
value.Type())
}
match.Ty = into
return match, nil
}
func (this *Tree) assembleMatchMap (match *entity.Match) (*entity.Match, error) {
match.CaseMap = make(map[entity.Hash] *entity.Case)
match.CaseOrder = make([]entity.Hash, len(match.Cases))
for index, cas := range match.Cases {
hash := cas.Declaration.Ty.Hash()
if previous, exists := match.CaseMap[hash]; exists {
return match, errors.Errorf (
cas.Declaration.Ty.Position(),
"%v already listed in match at %v",
cas.Declaration.Ty, previous.Declaration.Ty.Position())
}
match.CaseMap [hash] = cas
match.CaseOrder[index] = hash
match.Cases [index] = cas
}
return match, nil
}
func (this *Tree) analyzeLoop (
into entity.Type,
mode strictness,

81
analyzer/match_test.go Normal file
View File

@@ -0,0 +1,81 @@
package analyzer
import "testing"
func TestMatch (test *testing.T) {
testString (test,
`
U: (| Int F64)
[matchToInt u:U]:Int = match u in
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchReturn (test *testing.T) {
testString (test,
`
U: (| Int F64)
[isInt u:U]:Bool = {
match u in | u:Int [return true]
false
}
`)
}
func TestMatchUnionUnderComplete (test *testing.T) {
testString (test,
`
U: (| Int F64 UInt)
[print str:String]
[matchToInt u:U] = match u in
| u:Int [print 'Int']
| u:F64 [print 'F64']
`)
}
func TestMatchErrUnionOverComplete (test *testing.T) {
testStringErr (test,
"UInt is not included within U", 5, 6,
`
U: (| Int F64)
[matchToInt u:U]:Int = match u in
| u:Int u
| u:UInt [~Int u]
| u:F64 [~Int u]
`)
}
func TestMatchErrNotUnion (test *testing.T) {
testStringErr (test,
"type U is not a union", 3, 24,
`
U: Int
[matchToInt u:U]:Int = match u in
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchErrUnionUnderComplete (test *testing.T) {
testStringErr (test,
"match does not cover all types within U", 3, 24,
`
U: (| Int F64 UInt)
[matchToInt u:U]:Int = match u in
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchErrDuplicate (test *testing.T) {
testStringErr (test,
"Int already listed in match at stream0.fspl:4:6", 6, 6,
`
U: (| Int F64 UInt)
[matchToInt u:U]:Int = match u in
| u:Int u
| u:F64 [~Int u]
| u:Int u
`)
}

View File

@@ -61,12 +61,12 @@ func (this *Tree) analyzeMethod (
Position: method.Position,
Name: "this",
Ty: &entity.TypePointer {
Position: method.Position,
Pos: method.Position,
Referenced: &entity.TypeNamed {
Position: method.Position,
Unt: key.Unit,
Name: key.Name,
Type: owner.Type,
Pos: method.Position,
Unt: key.Unit,
Name: key.Name,
Type: owner.Type,
},
},
}

View File

@@ -104,11 +104,11 @@ func (this *Tree) analyzeTypeInternal (
if primitive, isPrimitive := primitiveTypes[ty.Name]; isPrimitive {
return primitive, nil
}
unit, err := this.resolveNickname(ty.Position, ty.UnitNickname)
unit, err := this.resolveNickname(ty.Position(), ty.UnitNickname)
if err != nil { return nil, err }
// analyze the typedef
def, err := this.analyzeTypedef(ty.Position, entity.Key {
def, err := this.analyzeTypedef(ty.Position(), entity.Key {
Unit: unit,
Name: ty.Name,
}, acceptIncomplete)
@@ -117,7 +117,7 @@ func (this *Tree) analyzeTypeInternal (
ty.Type = def.Type
// check access permissions
err = this.typePrivate(ty.Position, ty)
err = this.typePrivate(ty.Position(), ty)
if err != nil { return nil, err }
return ty, nil
@@ -145,7 +145,7 @@ func (this *Tree) analyzeTypeInternal (
updateIncompleteInfo()
if ty.Length < 1 {
return ty, errors.Errorf (
ty.Position, "array length must be > 0")
ty.Position(), "array length must be > 0")
}
ty.Element, err = this.analyzeType(ty.Element, false)
return ty, err
@@ -156,11 +156,11 @@ func (this *Tree) analyzeTypeInternal (
ty.Acc = access
ty, err = this.assembleStructMap(ty)
updateIncompleteInfo()
if err != nil { return ty, err }
if err != nil { return nil, err }
for name, member := range ty.MemberMap {
ty.MemberMap[name].Ty,
err = this.analyzeType(member.Ty, false)
if err != nil { return ty, err }
if err != nil { return nil, err }
}
return ty, nil
@@ -170,13 +170,20 @@ func (this *Tree) analyzeTypeInternal (
ty.Acc = access
ty, err = this.assembleInterfaceMap(ty)
updatePseudoCompleteInfo()
if err != nil { return ty, err }
if err != nil { return nil, err }
for name, behavior := range ty.BehaviorMap {
ty.BehaviorMap[name], err = this.analyzeBehavior(behavior)
if err != nil { return ty, err }
if err != nil { return nil, err }
}
return ty, nil
// union type
case *entity.TypeUnion:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
return this.analyzeTypeUnion(ty)
// integer type
case *entity.TypeInt:
ty.Unt = this.unit
@@ -184,7 +191,7 @@ func (this *Tree) analyzeTypeInternal (
updateIncompleteInfo()
if ty.Width < 1 {
return ty, errors.Errorf (
ty.Position, "integer width must be > 0")
ty.Position(), "integer width must be > 0")
}
return ty, nil
@@ -255,8 +262,8 @@ func (this *Tree) assembleInterfaceMap (ty *entity.TypeInterface) (*entity.TypeI
for index, method := range ty.Behaviors {
if previous, exists := ty.BehaviorMap[method.Name]; exists {
return ty, errors.Errorf (
method.Position, "%s already listed in interface at %v",
method.Name, previous.Position)
method.Position(), "%s already listed in interface at %v",
method.Name, previous.Position())
}
ty.BehaviorMap [method.Name] = method
ty.BehaviorOrder[index] = method.Name
@@ -265,6 +272,26 @@ func (this *Tree) assembleInterfaceMap (ty *entity.TypeInterface) (*entity.TypeI
return ty, nil
}
func (this *Tree) analyzeTypeUnion (ty *entity.TypeUnion) (*entity.TypeUnion, error) {
ty.AllowedMap = make(map[entity.Hash] entity.Type)
ty.AllowedOrder = make([]entity.Hash, len(ty.Allowed))
for index, allowed := range ty.Allowed {
allowed, err := this.analyzeType(allowed, true)
if err != nil { return nil, err }
hash := allowed.Hash()
if previous, exists := ty.AllowedMap[hash]; exists {
return ty, errors.Errorf (
allowed.Position(), "%v already listed in union at %v",
allowed, previous.Position())
}
ty.AllowedMap [hash] = allowed
ty.AllowedOrder[index] = hash
ty.Allowed [index] = allowed
}
return ty, nil
}
func (this *Tree) analyzeBehavior (behavior *entity.Signature) (*entity.Signature, error) {
behavior, err := this.assembleSignatureMap(behavior)
behavior.Unt = this.unit

View File

@@ -148,6 +148,50 @@ Bird: (.
`)
}
func TestTypeUnion (test *testing.T) {
testString (test,
`
U: (| I8 I16 I32 I64 Int)
Point: (. x:Int y:Int)
Error: (~ [error]:String)
PointOrError: (| Point Error)
`)
}
func TestTypeUnionAllowedUnique (test *testing.T) {
testString (test,
`
SomeInt: Int
U: (|
U8
U16
(. x:Int)
Int
(. x:SomeInt))
`)
}
func TestTypeUnionAllowedUniqueErrPrimitive (test *testing.T) {
testStringErr (test,
"Int already listed in union at stream0.fspl:2:10", 2, 26,
`
U: (| I8 Int I16 I32 I64 Int I64)
`)
}
func TestTypeUnionAllowedUniqueErrComposite (test *testing.T) {
testStringErr (test,
"(. x:Int) already listed in union at stream0.fspl:5:3", 7, 3,
`
U: (|
U8
U16
(. x:Int)
Int
(. x:Int))
`)
}
func TestTypeInterfaceBehaviorUniqueErr (test *testing.T) {
testStringErr (test,
"fly already listed in interface at stream0.fspl:2:10", 2, 16,

View File

@@ -64,3 +64,20 @@ testUnit (test,
"", "well hello their\n",
0,
)}
func TestMatchExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/matchexitcode", nil,
"", "",
7,
)}
func TestMatchPrint (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/matchprint", dependencies,
"", "F64\n",
0,
)}

View File

@@ -0,0 +1 @@
'839f0104-91f5-4db0-be52-6a17c571ebb1'

View File

@@ -0,0 +1,9 @@
[main]: I32 'main' = {
x:F32 = 7.7
[matchToInt x]
}
U: (| I32 F32)
[matchToInt u:U]:I32 = match u in
| u:I32 u
| u:F32 [~I32 u]

View File

@@ -0,0 +1,2 @@
'624d4557-5291-4ad7-9283-7c200b9c2942'
+ 'io'

View File

@@ -0,0 +1,10 @@
[main]: I32 'main' = {
x:F64 = 7.7
[matchToInt x]
0
}
U: (| Int F64 UInt)
[matchToInt u:U] = match u in
| u:Int io::[println 'Int']
| u:F64 io::[println 'F64']

View File

@@ -37,6 +37,14 @@ except it must have at least the methods defined within the interface.
Interfaces are always passed by reference. When assigning a value to an
interface, it will be referenced automatically. When assigning a pointer to an
interface, the pointer's reference will be used instead.
### Union
Union is a polymorphic type that can hold any value as long as it is one of a
list of allowed types. It is not a pointer. It holds the hash of the actual type
of the value stored within it, followed by the value. The hash field is computed
using the type's name, and the UUID that it was defined in. If it is not named,
then the hash is computed using the structure of the type. The value field is
always big enough to hold the largest type in the allowed list. The value of a
union can only be extracted using a match expression.
## Primitive types
### Int
@@ -89,15 +97,15 @@ type information. A value cast may be used for this purpose.
A string literal specifies a string value. It takes on different data
representations depending on what the base type of what it is assigned to is
structurally equivalent to:
- Integer: Single unicode code point. When assigning to an integer, the
string literal may not be longer than one code point, and that code point
must fit in the integer.
- Slice of 8 bit integers: UTF-8 string.
- Slice of 16 bit integers: UTF-16 string.
- Slice of 32 bit (or larger) integers: UTF-32 string.
- Array of integers: The same as slices of integers, but the string literal
must fit inside of the array.
- Pointer to 8 bit integer: Null-terminated UTF-8 string (AKA C-string).
- Integer: Single unicode code point. When assigning to an integer, the
string literal may not be longer than one code point, and that code point
must fit in the integer.
- Slice of 8 bit integers: UTF-8 string.
- Slice of 16 bit integers: UTF-16 string.
- Slice of 32 bit (or larger) integers: UTF-32 string.
- Array of integers: The same as slices of integers, but the string literal
must fit inside of the array.
- Pointer to 8 bit integer: Null-terminated UTF-8 string (AKA C-string).
A string literal cannot be directly assigned to an interface because it
contains no inherent type information. A value cast may be used for this
purpose.
@@ -240,6 +248,15 @@ If/else is a control flow branching expression that executes one of two
expressions depending on a boolean value. If the value of the if/else is unused,
the else expression need not be specified. It may be assigned to any type that
satisfies the assignment rules of both the true and false expressions.
### Match
Match is a control flow branching expression that executes one of several case
expressions depending on the input. It can be used to check the type of a union
value. Each case takes the form of a declaration, and an associated expression.
If the type of the union matches the type of the declaration in the case, the
expression is executed and the value of the union is made available to it
through the declaration. If the value of the match expression is used, all
possible types in the union must be accounted for. It may be assigned to any
type that satisfies the assignment rules of its first case.
### Loop
Loop is a control flow expression that repeats an expression until a break
statement is called from within it. The break statement must be given a value
@@ -286,6 +303,7 @@ this without hand-writing a parser.
<arrayType> -> <intLiteral> ":" <type>
<structType> -> "(" "." <declaration>* ")"
<interfaceType> -> "(" "~" <signature>* ")"
<unionType> -> "(" "|" <type>* ")"
<expression> -> <intLiteral>
| <floatLiteral>
@@ -319,7 +337,7 @@ this without hand-writing a parser.
<length> -> "[" "#" <expression> "]"
<dereference> -> "[" "." <expression> "]"
<reference> -> "[" "@" <expression> "]"
<valueCast> -> "[" "~" <type> <expression> "]"
<valueCast> -> "[" "~" <type> <expression> "]"
<bitCast> -> "[" "~~" <type> <expression> "]"
<operation> -> "[" <operator> <expression>* "]"
<block> -> "{" <expression>* "}"
@@ -328,6 +346,7 @@ this without hand-writing a parser.
<ifelse> -> "if" <expression>
"then" <expression>
["else" <expression>]
<match> -> "match" <expression> "in" <case>*
<loop> -> "loop" <expression>
<break> -> "[" "break" [<expression>] "]"
<return> -> "[" "return" [<expression>] "]"
@@ -344,6 +363,7 @@ this without hand-writing a parser.
<booleanLiteral> -> "true" | "false"
<member> -> <identifier> ":" <expression>
<case> -> "|" <declaration> <expression>
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
<identifier> -> /[a-z][A-Za-z]*/
<typeIdentifier> -> /[A-Z][A-Za-z]*/

View File

@@ -356,6 +356,42 @@ func (this *IfElse) String () string {
return out
}
// Match is a control flow branching expression that executes one of several
// case expressions depending on the input. It can be used to check the type of
// a union value. Each case takes the form of a declaration, and an associated
// expression. If the type of the union matches the type of the declaration in
// the case, the expression is executed and the value of the union is made
// available to it through the declaration. If the value of the match expression
// is used, all possible types in the union must be accounted for. It may be
// assigned to any type that satisfies the assignment rules of its first case.
type Match struct {
// Syntax
Position errors.Position
Value Expression
Cases []*Case
// Semantics
Ty Type
CaseOrder []Hash
CaseMap map[Hash] *Case
}
func (*Match) expression(){}
func (this *Match) Type () Type { return this.Ty }
func (this *Match) HasExplicitType () bool {
if len(this.Cases) == 0 {
return true
} else {
return this.Cases[0].HasExplicitType()
}
}
func (this *Match) String () string {
out := fmt.Sprint("match ", this.Value, " in")
for _, cas := range this.Cases {
out += fmt.Sprint(" ", cas)
}
return out
}
// Loop is a control flow expression that repeats an expression until a break
// statement is called from within it. The break statement must be given a value
// if the value of the loop is used. Otherwise, it need not even have a break

View File

@@ -1,6 +1,7 @@
package entity
import "fmt"
import "strings"
import "unicode"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
@@ -10,7 +11,7 @@ import "git.tebibyte.media/fspl/fspl/errors"
// function.
type Signature struct {
// Syntax
Position errors.Position
Pos errors.Position
Name string
Arguments []*Declaration
Return Type
@@ -22,6 +23,7 @@ type Signature struct {
ArgumentMap map[string] *Declaration
}
func (*Signature) ty(){}
func (this *Signature) Position () errors.Position { return this.Pos }
func (this *Signature) Access () Access { return this.Acc }
func (this *Signature) Unit () uuid.UUID { return this.Unt }
func (this *Signature) String () string {
@@ -35,6 +37,18 @@ func (this *Signature) String () string {
}
return out
}
func (this *Signature) Hash () Hash {
data := new(strings.Builder)
data.WriteString("Signature:")
for _, argument := range this.Arguments {
argumentHash := HashType(argument.Type())
data.Write(argumentHash[:])
}
data.WriteString(":")
returnHash := HashType(this.Return)
data.Write(returnHash[:])
return NewHash([]byte(data.String()))
}
func (this *Signature) Equals (ty Type) bool {
real, ok := ty.(*Signature)
if !ok ||
@@ -61,6 +75,17 @@ func (this *Member) String () string {
return fmt.Sprint(this.Name, ":", this.Value)
}
// Case represents a match case.
type Case struct {
Position errors.Position
Declaration *Declaration
Expression
Scope
}
func (this *Case) String () string {
return fmt.Sprint("| ", this.Declaration, this.Expression)
}
// Operator determines which operation to apply to a value in an operation
// expression.
type Operator int; const (

View File

@@ -1,10 +1,32 @@
package entity
import "fmt"
import "crypto/md5"
import "encoding/base64"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
// A hash represents a hash sum that fits within a uint64.
type Hash [8]byte
// NewHash creates a new truncated md5 hash using the specified data.
func NewHash (data []byte) Hash {
sum := md5.Sum(data)
hash := Hash { }
copy(hash[:], sum[:8])
return hash
}
// Number converts the hash into a uint64.
func (hash Hash) Number () uint64 {
var number uint64
for _, part := range hash {
number <<= 8
number |= uint64(part)
}
return number
}
// Key globally indexes top level entities in contexts where modules matter.
type Key struct {
Unit uuid.UUID
@@ -20,6 +42,12 @@ func (key Key) String () string {
return out
}
// Hash returns a representation of the hash of this key that fits within a
// uint64.
func (key Key) Hash () Hash {
return NewHash([]byte("Key:" + key.String()))
}
// LinkName returns the name that the entity it refers to will be given when
// compiled.
func (key Key) LinkName () string {

View File

@@ -1,6 +1,7 @@
package entity
import "fmt"
import "strings"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
@@ -8,6 +9,9 @@ import "git.tebibyte.media/fspl/fspl/errors"
type Type interface {
fmt.Stringer
// Position returns the position of the type within its source file.
Position () errors.Position
// Equals reports whether this type is equivalent to another type.
Equals (ty Type) bool
@@ -17,13 +21,16 @@ type Type interface {
// Unit reports the unit that the type was defined in.
Unit () uuid.UUID
// Hash returns a hash of this type that fits within a uint64.
Hash () Hash
ty ()
}
// TypeNamed refers to a user-defined or built in named type.
type TypeNamed struct {
// Syntax
Position errors.Position
Pos errors.Position
UnitNickname string
Name string
Type Type
@@ -33,6 +40,7 @@ type TypeNamed struct {
Unt uuid.UUID
}
func (*TypeNamed) ty(){}
func (this *TypeNamed) Position () errors.Position { return this.Pos }
func (this *TypeNamed) Access () Access { return this.Acc }
func (this *TypeNamed) Unit () uuid.UUID { return this.Unt }
func (this *TypeNamed) String () string {
@@ -42,6 +50,12 @@ func (this *TypeNamed) String () string {
return fmt.Sprint(this.UnitNickname, "::", this.Name)
}
}
func (this *TypeNamed) Hash () Hash {
return Key {
Unit: this.Type.Unit(),
Name: this.Name,
}.Hash()
}
func (this *TypeNamed) Equals (ty Type) bool {
real, ok := ty.(*TypeNamed)
return ok && TypesEqual(real.Type, this.Type)
@@ -49,7 +63,7 @@ func (this *TypeNamed) Equals (ty Type) bool {
// TypePointer is a pointer to another type.
type TypePointer struct {
Position errors.Position
Pos errors.Position
Referenced Type
// Semantics
@@ -57,11 +71,16 @@ type TypePointer struct {
Unt uuid.UUID
}
func (*TypePointer) ty(){}
func (this *TypePointer) Position () errors.Position { return this.Pos }
func (this *TypePointer) Access () Access { return this.Acc }
func (this *TypePointer) Unit () uuid.UUID { return this.Unt }
func (this *TypePointer) String () string {
return fmt.Sprint("*", this.Referenced)
}
func (this *TypePointer) Hash () Hash {
referenced := HashType(this.Referenced)
return NewHash(append([]byte("TypePointer:"), referenced[:]...))
}
func (this *TypePointer) Equals (ty Type) bool {
real, ok := ty.(*TypePointer)
return ok && TypesEqual(real.Referenced, this.Referenced)
@@ -71,7 +90,7 @@ func (this *TypePointer) Equals (ty Type) bool {
// eachother. Its length is not built into its type and can be changed at
// runtime.
type TypeSlice struct {
Position errors.Position
Pos errors.Position
Element Type
// Semantics
@@ -79,11 +98,16 @@ type TypeSlice struct {
Unt uuid.UUID
}
func (*TypeSlice) ty(){}
func (this *TypeSlice) Position () errors.Position { return this.Pos }
func (this *TypeSlice) Access () Access { return this.Acc }
func (this *TypeSlice) Unit () uuid.UUID { return this.Unt }
func (this *TypeSlice) String () string {
return fmt.Sprint("*:", this.Element)
}
func (this *TypeSlice) Hash () Hash {
referenced := HashType(this.Element)
return NewHash(append([]byte("TypeSlice:"), referenced[:]...))
}
func (this *TypeSlice) Equals (ty Type) bool {
real, ok := ty.(*TypeSlice)
return ok && TypesEqual(real.Element, this.Element)
@@ -93,7 +117,7 @@ func (this *TypeSlice) Equals (ty Type) bool {
// length of an array is fixed and is part of its type. Arrays are passed by
// value unless a pointer is used.
type TypeArray struct {
Position errors.Position
Pos errors.Position
Length int
Element Type
@@ -102,11 +126,18 @@ type TypeArray struct {
Unt uuid.UUID
}
func (*TypeArray) ty(){}
func (this *TypeArray) Position () errors.Position { return this.Pos }
func (this *TypeArray) Access () Access { return this.Acc }
func (this *TypeArray) Unit () uuid.UUID { return this.Unt }
func (this *TypeArray) String () string {
return fmt.Sprint(this.Length, ":", this.Element)
}
func (this *TypeArray) Hash () Hash {
referenced := HashType(this.Element)
return NewHash(append (
[]byte(fmt.Sprintf("TypeArray:%d:", this.Length)),
referenced[:]...))
}
func (this *TypeArray) Equals (ty Type) bool {
real, ok := ty.(*TypeArray)
return ok &&
@@ -119,7 +150,7 @@ func (this *TypeArray) Equals (ty Type) bool {
// are specified in. Structs are passed by value unless a pointer is used.
type TypeStruct struct {
// Syntax
Position errors.Position
Pos errors.Position
Members []*Declaration
// Semantics
@@ -129,6 +160,7 @@ type TypeStruct struct {
MemberMap map[string] *Declaration
}
func (*TypeStruct) ty(){}
func (this *TypeStruct) Position () errors.Position { return this.Pos }
func (this *TypeStruct) Access () Access { return this.Acc }
func (this *TypeStruct) Unit () uuid.UUID { return this.Unt }
func (this *TypeStruct) String () string {
@@ -138,6 +170,15 @@ func (this *TypeStruct) String () string {
}
return out + ")"
}
func (this *TypeStruct) Hash () Hash {
data := new(strings.Builder)
data.WriteString("TypeStruct:")
for _, member := range this.Members {
memberHash := HashType(member.Type())
data.Write(memberHash[:])
}
return NewHash([]byte(data.String()))
}
func (this *TypeStruct) Equals (ty Type) bool {
real, ok := ty.(*TypeStruct)
if !ok || len(real.Members) != len(this.Members) { return false }
@@ -157,7 +198,7 @@ func (this *TypeStruct) Equals (ty Type) bool {
// pointer to an interface, the pointer's reference will be used instead.
type TypeInterface struct {
// Syntax
Position errors.Position
Pos errors.Position
Behaviors []*Signature
// Semantics
@@ -167,6 +208,7 @@ type TypeInterface struct {
BehaviorMap map[string] *Signature
}
func (*TypeInterface) ty(){}
func (this *TypeInterface) Position () errors.Position { return this.Pos }
func (this *TypeInterface) Access () Access { return this.Acc }
func (this *TypeInterface) Unit () uuid.UUID { return this.Unt }
func (this *TypeInterface) String () string {
@@ -176,6 +218,15 @@ func (this *TypeInterface) String () string {
}
return out + ")"
}
func (this *TypeInterface) Hash () Hash {
data := new(strings.Builder)
data.WriteString("TypeInterface:")
for _, behavior := range this.Behaviors {
behaviorHash := HashType(behavior)
data.Write(behaviorHash[:])
}
return NewHash([]byte(data.String()))
}
func (this *TypeInterface) Equals (ty Type) bool {
real, ok := ty.(*TypeInterface)
if !ok || len(real.Behaviors) != len(this.Behaviors) { return false }
@@ -188,9 +239,59 @@ func (this *TypeInterface) Equals (ty Type) bool {
return true
}
// TypeUnion is a polymorphic type that can hold any value as long as it is one
// of a list of allowed types. It is not a pointer. It holds the hash of the
// actual type of the value stored within it, followed by the value. The hash
// field is computed using the type's name, and the UUID that it was defined in.
// If it is not named, then the hash is computed using the structure of the
// type. The value field is always big enough to hold the largest type in the
// allowed list.
type TypeUnion struct {
// Syntax
Pos errors.Position
Allowed []Type
// Semantics
Acc Access
Unt uuid.UUID
AllowedOrder []Hash
AllowedMap map[Hash] Type
}
func (*TypeUnion) ty(){}
func (this *TypeUnion) Position () errors.Position { return this.Pos }
func (this *TypeUnion) Access () Access { return this.Acc }
func (this *TypeUnion) Unit () uuid.UUID { return this.Unt }
func (this *TypeUnion) String () string {
out := "(|"
for _, ty := range this.Allowed {
out += fmt.Sprint(" ", ty)
}
return out + ")"
}
func (this *TypeUnion) Hash () Hash {
data := new(strings.Builder)
data.WriteString("TypeUnion:")
for _, ty := range this.Allowed {
typeHash := HashType(ty)
data.Write(typeHash[:])
}
return NewHash([]byte(data.String()))
}
func (this *TypeUnion) Equals (ty Type) bool {
real, ok := ty.(*TypeUnion)
if !ok || len(real.Allowed) != len(this.Allowed) { return false }
for index, ty := range this.Allowed {
if !ty.Equals(real.Allowed[index]) {
return false
}
}
return true
}
// TypeInt represents any signed or unsigned integer type.
type TypeInt struct {
Position errors.Position
Pos errors.Position
Width int
Signed bool
@@ -199,6 +300,7 @@ type TypeInt struct {
Unt uuid.UUID
}
func (*TypeInt) ty(){}
func (this *TypeInt) Position () errors.Position { return this.Pos }
func (this *TypeInt) Access () Access { return this.Acc }
func (this *TypeInt) Unit () uuid.UUID { return this.Unt }
func (this *TypeInt) String () string {
@@ -208,6 +310,11 @@ func (this *TypeInt) String () string {
return fmt.Sprint("U", this.Width)
}
}
func (this *TypeInt) Hash () Hash {
return NewHash([]byte(fmt.Sprintf (
"TypeInt:%d:%t",
this.Width, this.Signed)))
}
func (this *TypeInt) Equals (ty Type) bool {
real, ok := ty.(*TypeInt)
return ok && real.Width == this.Width && real.Signed == this.Signed
@@ -215,7 +322,7 @@ func (this *TypeInt) Equals (ty Type) bool {
// TypeFloat represents any floating point type.
type TypeFloat struct {
Position errors.Position
Pos errors.Position
Width int
// Semantics
@@ -223,11 +330,15 @@ type TypeFloat struct {
Unt uuid.UUID
}
func (*TypeFloat) ty(){}
func (this *TypeFloat) Position () errors.Position { return this.Pos }
func (this *TypeFloat) Access () Access { return this.Acc }
func (this *TypeFloat) Unit () uuid.UUID { return this.Unt }
func (this *TypeFloat) String () string {
return fmt.Sprint("F", this.Width)
}
func (this *TypeFloat) Hash () Hash {
return NewHash([]byte(fmt.Sprintf("TypeFloat:%d", this.Width)))
}
func (this *TypeFloat) Equals (ty Type) bool {
real, ok := ty.(*TypeFloat)
return ok && real.Width == this.Width
@@ -237,7 +348,7 @@ func (this *TypeFloat) Equals (ty Type) bool {
// is chosen based on the machine word size (32 on 32 bit systems, 64 on 64 bit
// systems, etc)
type TypeWord struct {
Position errors.Position
Pos errors.Position
Signed bool
// Semantics
@@ -245,6 +356,7 @@ type TypeWord struct {
Unt uuid.UUID
}
func (*TypeWord) ty(){}
func (this *TypeWord) Position () errors.Position { return this.Pos }
func (this *TypeWord) Access () Access { return this.Acc }
func (this *TypeWord) Unit () uuid.UUID { return this.Unt }
func (this *TypeWord) String () string {
@@ -254,6 +366,9 @@ func (this *TypeWord) String () string {
return "UInt"
}
}
func (this *TypeWord) Hash () Hash {
return NewHash([]byte(fmt.Sprintf("TypeWord:%t", this.Signed)))
}
func (this *TypeWord) Equals (ty Type) bool {
real, ok := ty.(*TypeWord)
return ok && real.Signed == this.Signed
@@ -276,3 +391,13 @@ func FormatType (ty Type) string {
return ty.String()
}
}
// HashType returns a hash representing a type. If the type is nil, it returns
// NewHash([]byte("TypeVoid"))
func HashType (ty Type) Hash {
if ty == nil {
return NewHash([]byte("TypeVoid"))
} else {
return ty.Hash()
}
}

View File

@@ -139,6 +139,34 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
}
// conversion from any type to union
case *entity.TypeUnion:
// only proceed if the types are different
if entity.TypesEqual(source.Type(), destType) { break }
irHashType := llvm.I64
// create destination union, if necessary
irDestType, err := this.generateType(destType)
if err != nil { return nil, err }
if !destinationSpecified {
irDestLoc = this.blockManager.newAllocaFront(irDestType)
}
destTypeFieldLoc := this.getUnionTypeFieldLoc(irDestLoc, irDestType)
destDataFieldLoc := this.getUnionDataFieldLoc(irDestLoc, irDestType)
// store type hash
hash := source.Type().Hash()
this.blockManager.NewStore (
llvm.NewConstInt(irHashType, int64(hash.Number())),
destTypeFieldLoc)
// store data
source, err := this.generateExpressionVal(source)
if err != nil { return nil, err }
this.blockManager.NewStore(source, destDataFieldLoc)
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
// conversion from any type to pointer
case *entity.TypePointer:
// assignments

View File

@@ -156,3 +156,145 @@ testString (test,
}
`)
}
func TestMatch (test *testing.T) {
testString (test,
`%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::matchToInt"(%"0zNZN147MN2wzMAQ6NS2dQ==::U" %u) {
0:
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
store %"0zNZN147MN2wzMAQ6NS2dQ==::U" %u, ptr %1
%2 = alloca i64
%3 = alloca double
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 0
%5 = load i64, ptr %4
switch i64 %5, label %6 [
i64 7620767046192759206, label %8
i64 9186060094042213285, label %12
]
6:
%7 = phi i64 [ %11, %8 ], [ %16, %12 ], [ zeroinitializer, %0 ]
ret i64 %7
8:
%9 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%10 = load i64, ptr %9
store i64 %10, ptr %2
%11 = load i64, ptr %2
br label %6
12:
%13 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%14 = load double, ptr %13
store double %14, ptr %3
%15 = load double, ptr %3
%16 = fptosi double %15 to i64
br label %6
}
`,
`
U: (| Int F64)
[matchToInt u:U]:Int = match u in
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchReturn (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i8
%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::isInt"(%"0zNZN147MN2wzMAQ6NS2dQ==::U" %u) {
0:
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
store %"0zNZN147MN2wzMAQ6NS2dQ==::U" %u, ptr %1
%2 = alloca i64
%3 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 0
%4 = load i64, ptr %3
switch i64 %4, label %5 [
i64 7620767046192759206, label %6
]
5:
ret i1 false
6:
%7 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%8 = load i64, ptr %7
store i64 %8, ptr %2
ret i1 true
}
`,
`
U: (| Int F64)
[isInt u:U]:Bool = {
match u in | u:Int [return true]
false
}
`)
}
func TestMatchUnionUnderComplete (test *testing.T) {
testString (test,
`%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
define void @"0zNZN147MN2wzMAQ6NS2dQ==::matchToInt"(%"0zNZN147MN2wzMAQ6NS2dQ==::U" %u) {
0:
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
store %"0zNZN147MN2wzMAQ6NS2dQ==::U" %u, ptr %1
%2 = alloca i64
%3 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
%4 = alloca [3 x i8]
%5 = alloca double
%6 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
%7 = alloca [3 x i8]
%8 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 0
%9 = load i64, ptr %8
switch i64 %9, label %10 [
i64 7620767046192759206, label %11
i64 9186060094042213285, label %20
]
10:
ret void
11:
%12 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%13 = load i64, ptr %12
store i64 %13, ptr %2
%14 = getelementptr [3 x i8], ptr %4, i32 0, i32 0
store i8 73, ptr %14
%15 = getelementptr [3 x i8], ptr %4, i32 0, i32 1
store i8 110, ptr %15
%16 = getelementptr [3 x i8], ptr %4, i32 0, i32 2
store i8 116, ptr %16
%17 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3, i32 0, i32 0
store ptr %4, ptr %17
%18 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %18
%19 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %19)
br label %10
20:
%21 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%22 = load double, ptr %21
store double %22, ptr %5
%23 = getelementptr [3 x i8], ptr %7, i32 0, i32 0
store i8 70, ptr %23
%24 = getelementptr [3 x i8], ptr %7, i32 0, i32 1
store i8 54, ptr %24
%25 = getelementptr [3 x i8], ptr %7, i32 0, i32 2
store i8 52, ptr %25
%26 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %6, i32 0, i32 0
store ptr %7, ptr %26
%27 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %6, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %27
%28 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %6
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %28)
br label %10
}
declare void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %str)
`,
`
U: (| Int F64 UInt)
[print str:String]
[matchToInt u:U] = match u in
| u:Int [print 'Int']
| u:F64 [print 'F64']
`)
}

View File

@@ -115,3 +115,19 @@ func (this *generator) getStructMemberIndex (ty *entity.TypeStruct, name string)
}
return offset
}
func (this *generator) getUnionTypeFieldLoc (union llvm.Value, irType llvm.Type) llvm.Value {
irType = llvm.ReduceToBase(irType)
return this.blockManager.NewGetElementPtr (
irType, union,
llvm.NewConstInt(llvm.I32, 0),
llvm.NewConstInt(llvm.I32, 0))
}
func (this *generator) getUnionDataFieldLoc (union llvm.Value, irType llvm.Type) llvm.Value {
irType = llvm.ReduceToBase(irType)
return this.blockManager.NewGetElementPtr (
irType, union,
llvm.NewConstInt(llvm.I32, 0),
llvm.NewConstInt(llvm.I32, 1))
}

View File

@@ -93,6 +93,8 @@ func (this *generator) generateExpressionAny (expression entity.Expression) (reg
return this.generateBlock(expression, resultModeAny)
case *entity.IfElse:
return this.generateIfElse(expression, resultModeAny)
case *entity.Match:
return this.generateMatch(expression, resultModeAny)
case *entity.Loop:
return this.generateLoop(expression, resultModeAny)
@@ -148,14 +150,17 @@ func (this *generator) generateExpressionVal (expression entity.Expression) (llv
case *entity.Operation:
return this.generateOperationVal(expression)
case *entity.Block:
loc, _, err := this.generateBlock(expression, resultModeVal)
return loc, err
val, _, err := this.generateBlock(expression, resultModeVal)
return val, err
case *entity.IfElse:
loc, _, err := this.generateIfElse(expression, resultModeVal)
return loc, err
val, _, err := this.generateIfElse(expression, resultModeVal)
return val, err
case *entity.Match:
val, _, err := this.generateMatch(expression, resultModeVal)
return val, err
case *entity.Loop:
loc, _, err := this.generateLoop(expression, resultModeVal)
return loc, err
val, _, err := this.generateLoop(expression, resultModeVal)
return val, err
case *entity.LiteralInt:
return this.generateLiteralInt(expression)
case *entity.LiteralFloat:
@@ -222,6 +227,9 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
case *entity.IfElse:
loc, _, err := this.generateIfElse(expression, resultModeLoc)
return loc, err
case *entity.Match:
loc, _, err := this.generateMatch(expression, resultModeLoc)
return loc, err
case *entity.Loop:
loc, _, err := this.generateLoop(expression, resultModeLoc)
return loc, err

View File

@@ -94,6 +94,72 @@ func (this *generator) generateIfElse (ifelse *entity.IfElse, mode resultMode) (
}
}
func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llvm.Value, bool, error) {
var loc bool
value, err := this.generateExpressionLoc(match.Value)
if err != nil { return nil, false, err }
irUnionType, err := this.generateType(match.Value.Type())
if err != nil { return nil, false, err }
previousBlock := this.blockManager.Block
exitBlock := this.blockManager.newBlock()
irHashType := llvm.I64
irCases := make([]*llvm.Case, len(match.Cases))
irIncomings := make([]*llvm.Incoming, len(match.Cases))
for index, cas := range match.Cases {
// set up ir case
caseBlock := this.blockManager.newBlock()
hash := match.CaseOrder[index]
irCases[index] = &llvm.Case {
X: llvm.NewConstInt(irHashType, int64(hash.Number())),
Target: caseBlock,
}
// create casted variable
irType, err := this.generateType(cas.Declaration.Type())
if err != nil { return nil, false, err }
dataFieldLoc := this.getUnionDataFieldLoc(value, irUnionType)
data := this.blockManager.NewLoad(irType, dataFieldLoc)
this.blockManager.addDeclaration(cas.Declaration, data)
// generate case expression
thisMode := mode // FIXME this logic isnt in if/else, why??
if index != 0 && mode != resultModeAny {
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
}
caseExpression, thisLoc, err := this.generateExpression(cas.Expression, thisMode)
if err != nil { return nil, false, err }
if index == 0 { loc = thisLoc }
irIncomings[index] = &llvm.Incoming {
X: caseExpression,
Predecessor: caseBlock,
}
if !this.blockManager.Terminated() { this.blockManager.NewBr(exitBlock) }
}
// create switch branch
this.blockManager.Block = previousBlock
hashFieldLoc := this.getUnionTypeFieldLoc(value, irUnionType)
hash := this.blockManager.NewLoad(irHashType, hashFieldLoc)
this.blockManager.NewSwitch(hash, exitBlock, irCases...)
// discard/obtain results
this.blockManager.Block = exitBlock
if mode == resultModeAny {
return nil, false, nil
} else {
irType, err := this.generateType(match.Type())
if err != nil { return nil, false, err }
irIncomings = append(irIncomings, &llvm.Incoming {
X: llvm.NewConstZeroInitializer(irType),
Predecessor: previousBlock,
})
return this.blockManager.NewPhi(irIncomings...), loc, nil
}
}
func (this *generator) generateLoop (loop *entity.Loop, mode resultMode) (llvm.Value, bool, error) {
previous := this.blockManager.Block
body := this.blockManager.newBlock()

View File

@@ -37,6 +37,8 @@ func (this *generator) generateType (ty entity.Type) (llvm.Type, error) {
return this.generateTypeStruct(ty.(*entity.TypeStruct))
case *entity.TypeInterface:
return this.generateTypeInterface(ty.(*entity.TypeInterface))
case *entity.TypeUnion:
return this.generateTypeUnion(ty.(*entity.TypeUnion))
case *entity.TypeInt:
return this.generateTypeInt(ty.(*entity.TypeInt))
case *entity.TypeFloat:

View File

@@ -72,6 +72,26 @@ func (this *generator) generateTypeInterface (ty *entity.TypeInterface) (llvm.Ty
return irStruct, nil
}
func (this *generator) generateTypeUnion (ty *entity.TypeUnion) (llvm.Type, error) {
size := uint64(0)
for _, allowed := range ty.Allowed {
irAllowed, err := this.generateType(allowed)
if err != nil { return nil, err }
allowedSize := this.sizeOfIrType(irAllowed)
if allowedSize > size { size = allowedSize }
}
irStruct := &llvm.TypeStruct {
Fields: []llvm.Type {
// TODO: could this field be smaller? for example what
// does rust do?
&llvm.TypeInt { BitSize: 64 },
&llvm.TypeInt { BitSize: size },
},
}
return irStruct, nil
}
func (this *generator) generateTypeInt (ty *entity.TypeInt) (llvm.Type, error) {
return &llvm.TypeInt { BitSize: uint64(ty.Width) }, nil
}
@@ -125,6 +145,60 @@ func (this *generator) generateTypeFunction (
return irFunc, nil
}
func (this *generator) sizeOfIrType (ty llvm.Type) uint64 {
switch ty := ty.(type) {
case *llvm.TypeArray:
return this.alignmentScale(this.sizeOfIrType(ty.Element)) * ty.Length
case *llvm.TypeVector:
return this.alignmentScale(this.sizeOfIrType(ty.Element)) * ty.Length
case *llvm.TypeDefined:
return this.sizeOfIrType(ty.Source)
case *llvm.TypeFloat:
switch ty.Kind {
case llvm.FloatKindHalf: return 16
case llvm.FloatKindFloat: return 32
case llvm.FloatKindDouble: return 64
case llvm.FloatKindFP128: return 128
case llvm.FloatKindX86_FP80: return 80
case llvm.FloatKindPPC_FP128: return 128
}
case *llvm.TypeFunction: return 0
case *llvm.TypeInt: return ty.BitSize
case *llvm.TypeLabel: return 0
case *llvm.TypeMMX: return 0 // is this correct?
case *llvm.TypeMetadata: return 0
case *llvm.TypePointer: return this.target.WordSize
case *llvm.TypeStruct:
// TODO ensure this is correct because it might not be
total := uint64(0)
for _, field := range ty.Fields {
fieldSize := this.sizeOfIrType(field)
if !ty.Packed {
// if not packed, align members
empty := total == 0
fieldSize = this.alignmentScale(fieldSize)
total /= fieldSize
if !empty && total == 0 { total ++ }
total *= fieldSize
}
total += fieldSize
}
return total
case *llvm.TypeToken: return 0
case *llvm.TypeVoid: return 0
}
panic(fmt.Sprintln("generator doesn't know about LLVM type", ty))
}
// alignmentScale returns the smallest power of two that is greater than or
// equal to size. Note that it starts at 8.
func (this *generator) alignmentScale (size uint64) uint64 {
scale := uint64(8)
for size > scale { scale *= 2 }
return scale
}
func getInterface (ty entity.Type) *entity.TypeInterface {
switch ty.(type) {
case *entity.TypeNamed:

View File

@@ -129,3 +129,37 @@ B: A
}
`)
}
func TestTypeUnion (test *testing.T) {
testString (test,
`%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
%"0zNZN147MN2wzMAQ6NS2dQ==::SmallU" = type { i64, i16 }
%"0zNZN147MN2wzMAQ6NS2dQ==::Padded" = type { i8, i16 }
%"0zNZN147MN2wzMAQ6NS2dQ==::PaddedU" = type { i64, i32 }
%"0zNZN147MN2wzMAQ6NS2dQ==::Point" = type { i64, i64 }
%"0zNZN147MN2wzMAQ6NS2dQ==::Error" = type { ptr, ptr }
%"0zNZN147MN2wzMAQ6NS2dQ==::PointOrError" = type { i64, i128 }
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
0:
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::SmallU"
%3 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::PaddedU"
%4 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::PointOrError"
ret void
}
`,
`
U: (| I8 I16 I32 I64 Int)
SmallU: (| I8 U8 I16 U16)
Point: (. x:Int y:Int)
Padded: (. a:I8 b:I16)
PaddedU: (| Padded)
Error: (~ [error]:String)
PointOrError: (| Point Error)
[main] = {
u:U
su:SmallU
pu:PaddedU
pe:PointOrError
}
`)}

48
generator/union_test.go Normal file
View File

@@ -0,0 +1,48 @@
package generator
import "testing"
func TestAssignmentUnion (test *testing.T) {
testString (test,
`%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
0:
%1 = alloca double
store double 0x401ECCCCCCCCCCCD, ptr %1
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
%3 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %2, i32 0, i32 0
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %2, i32 0, i32 1
store i64 9186060094042213285, ptr %3
%5 = load double, ptr %1
store double %5, ptr %4
%6 = load %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %2
ret void
}
`,
`
U: (| Int F64)
[main] = {
x:F64 = 7.7
y:U = x
}
`)}
func TestAssignmentUnionToUnion (test *testing.T) {
testString (test,
`%"0zNZN147MN2wzMAQ6NS2dQ==::U" = type { i64, i64 }
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
0:
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::U"
%3 = load %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %2
store %"0zNZN147MN2wzMAQ6NS2dQ==::U" %3, ptr %1
ret void
}
`,
`
U: (| Int F64)
[main] = {
x:U y:U
x = y
}
`)}

View File

@@ -69,6 +69,7 @@ expression, the parser follows this decision tree to determine what to parse:
| | 'false' =LiteralBoolean
| | 'nil' =LiteralNil
| | 'if' =IfElse
| | 'match' =Match
| | 'loop' =Loop
| | +Colon =Declaration
| | +DoubleColon =Call
@@ -120,8 +121,11 @@ determine what type to parse:
| +Int =TypeArray
|
| +LParen X
| | +Dot =TypeStruct
| | +Symbol '~' =TypeInterface
| | +Dot =TypeStruct
| |
| | +Symbol X
| | '~' =TypeInterface
| | '|' =TypeSum
|
| +Star =TypePointer
| +Colon =TypeSlice

View File

@@ -116,6 +116,7 @@ func (this *treeParser) parseExpressionRootIdent () (entity.Expression, error) {
case "true", "false": return this.parseLiteralBoolean ()
case "nil": return this.parseLiteralNil()
case "if": return this.parseIfElse()
case "match": return this.parseMatch()
case "loop": return this.parseLoop()
default:
this.Next()
@@ -563,6 +564,47 @@ func (this *treeParser) parseIfElse () (*entity.IfElse, error) {
return ifElse, nil
}
func (this *treeParser) parseMatch () (*entity.Match, error) {
err := this.ExpectValue(lexer.Ident, "match")
if err != nil { return nil, err }
match := &entity.Match {
Position: this.Pos(),
}
err = this.ExpectNextDesc(descriptionExpression, startTokensExpression...)
if err != nil { return nil, err }
match.Value, err = this.parseExpression()
if err != nil { return nil, err }
err = this.ExpectValue(lexer.Ident, "in")
if err != nil { return nil, err }
this.Next()
for this.Is(lexer.Symbol) && this.ValueIs("|") {
cas, err := this.parseCase()
if err != nil { return nil, err }
match.Cases = append(match.Cases, cas)
}
return match, nil
}
func (this *treeParser) parseCase () (*entity.Case, error) {
err := this.ExpectValue(lexer.Symbol, "|")
if err != nil { return nil, err }
cas := &entity.Case {
Position: this.Pos(),
}
this.Next()
cas.Declaration, err = this.parseDeclaration()
if err != nil { return nil, err }
cas.Expression, err = this.parseExpression()
if err != nil { return nil, err }
return cas, nil
}
func (this *treeParser) parseLoop () (*entity.Loop, error) {
err := this.ExpectValue(lexer.Ident, "loop")
if err != nil { return nil, err }

View File

@@ -14,8 +14,8 @@ func (this *treeParser) parseSignature () (*entity.Signature, error) {
err = this.ExpectNext(lexer.Ident)
if err != nil { return nil, err }
signature := &entity.Signature{
Position: pos,
Name: this.Value(),
Pos: pos,
Name: this.Value(),
}
this.Next()
@@ -30,7 +30,7 @@ func (this *treeParser) parseSignature () (*entity.Signature, error) {
if err != nil { return nil, err }
signature.Arguments = append(signature.Arguments, parameter)
}
signature.Position = signature.Position.Union(this.Pos())
signature.Pos = signature.Position().Union(this.Pos())
this.Next()
if this.Kind() == lexer.Colon {

View File

@@ -8,6 +8,7 @@ testString (test,
`- BasicInt: Int
- Structure: (. x:Int y:Int)
- Interface: (~ [aMethod x:Int]:*U8 [otherMethod arg:5:Int]:*U8)
- Union: (| Int F64 *:U8 (. x:Int y:Int))
- Array: 16:16:16:Int
- StructArray: 31:24:340920:(. x:Int y:Int)
- String: *:U8`,
@@ -20,6 +21,9 @@ Structure: (.
Interface: (~
[aMethod x:Int]:*U8
[otherMethod arg:5:Int]:*U8)
- Union: (|
Int F64 *:U8
(. x:Int y:Int))
Array: 16:16:16:Int
StructArray: 31:24:340920:(.
x:Int
@@ -67,7 +71,8 @@ testString (test,
- [math] = {[+ 3 4 2 [/ 24 3]] [|| 3 [<< 56 4]] [++ 3] [-- 3] [- 3 1]}
- [ifElse]:Int = if true then 4 else 5
- [dangleElse]:Int = if true then if false then 3 else 5
- [loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}`,
- [loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}
- [matchToInt u:(| Int F64)]:Int = match u in | u:Int u | u:F64 [~ Int u]`,
// input
`
[var] = sdfdf
@@ -113,6 +118,9 @@ testString (test,
i = [++ i]
}
}
[matchToInt u:(| Int F64)]:Int = match u in
| u:Int u
| u:F64 [~Int u]
`)
}

View File

@@ -86,7 +86,7 @@ func (this *treeParser) parseFunctionCore (pos errors.Position, access entity.Ac
if err != nil { return nil, err }
function := &entity.Function {
Position: pos.Union(signature.Position),
Position: pos.Union(signature.Position()),
Acc: access,
Signature: signature,
}

View File

@@ -64,6 +64,7 @@ func (this *treeParser) parseType () (entity.Type, error) {
switch this.Value() {
case ".": return this.parseTypeStructCore()
case "~": return this.parseTypeInterfaceCore()
case "|": return this.parseTypeUnionCore()
}
panic(this.bug())
}
@@ -76,7 +77,7 @@ func (this *treeParser) parseTypeNamedCore (pos errors.Position, unitNickname st
defer this.Next()
return &entity.TypeNamed {
Position: pos.Union(this.Pos()),
Pos: pos.Union(this.Pos()),
Name: this.Value(),
UnitNickname: unitNickname,
}, nil
@@ -97,14 +98,14 @@ func (this *treeParser) parseTypePointerOrSlice () (entity.Type, error) {
element, err := this.parseType()
if err != nil { return nil, err }
return &entity.TypeSlice {
Position: start.Union(this.Pos()),
Pos: start.Union(this.Pos()),
Element: element,
}, nil
} else {
referenced, err := this.parseType()
if err != nil { return nil, err }
return &entity.TypePointer {
Position: start.Union(this.Pos()),
Pos: start.Union(this.Pos()),
Referenced: referenced,
}, nil
}
@@ -125,7 +126,7 @@ func (this *treeParser) parseTypeArray () (entity.Type, error) {
if err != nil { return nil, err }
return &entity.TypeArray {
Position: start.Union(this.Pos()),
Pos: start.Union(this.Pos()),
Length: length,
Element: element,
}, nil
@@ -135,7 +136,7 @@ func (this *treeParser) parseTypeStructCore () (entity.Type, error) {
err := this.Expect(lexer.Dot)
if err != nil { return nil, err }
ty := &entity.TypeStruct {
Position: this.Pos(),
Pos: this.Pos(),
}
this.Next()
@@ -150,7 +151,7 @@ func (this *treeParser) parseTypeStructCore () (entity.Type, error) {
if err != nil { return nil, err }
ty.Members = append(ty.Members, member)
}
ty.Position = ty.Position.Union(this.Pos())
ty.Pos = ty.Position().Union(this.Pos())
this.Next()
return ty, nil
}
@@ -159,7 +160,7 @@ func (this *treeParser) parseTypeInterfaceCore () (entity.Type, error) {
err := this.ExpectValue(lexer.Symbol, "~")
if err != nil { return nil, err }
ty := &entity.TypeInterface {
Position: this.Pos(),
Pos: this.Pos(),
}
this.Next()
@@ -174,7 +175,31 @@ func (this *treeParser) parseTypeInterfaceCore () (entity.Type, error) {
if err != nil { return nil, err }
ty.Behaviors = append(ty.Behaviors, behavior)
}
ty.Position = ty.Position.Union(this.Pos())
ty.Pos = ty.Position().Union(this.Pos())
this.Next()
return ty, nil
}
func (this *treeParser) parseTypeUnionCore () (entity.Type, error) {
err := this.ExpectValue(lexer.Symbol, "|")
if err != nil { return nil, err }
ty := &entity.TypeUnion {
Pos: this.Pos(),
}
this.Next()
for {
err := this.ExpectDesc (
"allowed type or end",
appendCopy(startTokensType, lexer.RParen)...)
if err != nil { return nil, err }
if this.Kind() == lexer.RParen { break }
allowed, err := this.parseType()
if err != nil { return nil, err }
ty.Allowed = append(ty.Allowed, allowed)
}
ty.Pos = ty.Position().Union(this.Pos())
this.Next()
return ty, nil
}
@@ -191,9 +216,9 @@ func (this *treeParser) parseTypeInt () (entity.Type, error) {
defer this.Next()
return &entity.TypeInt {
Position: this.Pos(),
Width: width,
Signed: value[0] == 'I',
Pos: this.Pos(),
Width: width,
Signed: value[0] == 'I',
}, nil
}
@@ -202,8 +227,8 @@ func (this *treeParser) parseTypeWord () (entity.Type, error) {
if err != nil { return nil, err }
defer this.Next()
return &entity.TypeWord {
Position: this.Pos(),
Signed: this.Value()[0] == 'I',
Pos: this.Pos(),
Signed: this.Value()[0] == 'I',
}, nil
}
@@ -215,7 +240,7 @@ func (this *treeParser) parseTypeFloat () (entity.Type, error) {
defer this.Next()
return &entity.TypeFloat {
Position: this.Pos(),
Width: width,
Pos: this.Pos(),
Width: width,
}, nil
}