Merge pull request 'implement-union-types' (#54) from implement-union-types into main
Reviewed-on: #54
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
81
analyzer/match_test.go
Normal 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
|
||||
`)
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)}
|
||||
|
||||
1
compiler/test-data/data/matchexitcode/fspl.mod
Normal file
1
compiler/test-data/data/matchexitcode/fspl.mod
Normal file
@@ -0,0 +1 @@
|
||||
'839f0104-91f5-4db0-be52-6a17c571ebb1'
|
||||
9
compiler/test-data/data/matchexitcode/main.fspl
Normal file
9
compiler/test-data/data/matchexitcode/main.fspl
Normal 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]
|
||||
2
compiler/test-data/data/matchprint/fspl.mod
Normal file
2
compiler/test-data/data/matchprint/fspl.mod
Normal file
@@ -0,0 +1,2 @@
|
||||
'624d4557-5291-4ad7-9283-7c200b9c2942'
|
||||
+ 'io'
|
||||
10
compiler/test-data/data/matchprint/main.fspl
Normal file
10
compiler/test-data/data/matchprint/main.fspl
Normal 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']
|
||||
@@ -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]*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
143
entity/type.go
143
entity/type.go
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
48
generator/union_test.go
Normal 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
|
||||
}
|
||||
`)}
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
`)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user