Merge pull request 'implement-switch-expressions' (#70) from implement-switch-expressions into main

Reviewed-on: #70
This commit is contained in:
Sasha Koshka 2024-03-26 04:55:45 +00:00
commit ebafbc2a38
18 changed files with 701 additions and 39 deletions

View File

@ -475,7 +475,7 @@ func isNumeric (ty entity.Type) bool {
}
}
// isInteger returns whether or not the specified type is an integer.
// isInteger returns whether or not the specified type is an integer or word.
func isInteger (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeInt, *entity.TypeWord: return true
@ -483,6 +483,22 @@ func isInteger (ty entity.Type) bool {
}
}
// isWord returns whether or not the specified type is a word.
func isWord (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeWord: return true
default: return false
}
}
// isPointer returns whether or not the specified type is a pointer.
func isPointer (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypePointer: return true
default: return false
}
}
// isOrdered returns whether or not the values of the specified type can be
// ordered.
func isOrdered (ty entity.Type) bool {
@ -515,6 +531,15 @@ func IsUnsigned (ty entity.Type) bool {
}
}
// integerWidth returns the width of the type, if it is an integer.
func integerWidth (ty entity.Type) (int, bool) {
switch ty := ReduceToBase(ty).(type) {
case *entity.TypeInt: return ty.Width, true
default: return 0, false
}
}
// inRange returns whether the specified value can fit within the given integer
// type.
func inRange (ty entity.Type, value int64) bool {

View File

@ -33,6 +33,16 @@ U: (| Int F64)
`)
}
func TestMatchDefault (test *testing.T) {
testString (test,
`
U: (| Int F64)
[isInt u:U]:Bool = match u
| u:Int [return true]
* false
`)
}
func TestMatchUnionUnderComplete (test *testing.T) {
testString (test,
`
@ -90,6 +100,78 @@ U: (| Int F64 UInt)
`)
}
func TestSwitch (test *testing.T) {
testString (test,
`
[switch x:Int]:Int = switch x
| 0 5
| 1 4
| 2 3
* 0
`)
}
func TestSwitchReturn (test *testing.T) {
testString (test,
`
[is5 x:Int]:Bool = {
switch x | 5 [return true]
false
}
`)
}
func TestSwitchReturnValueUsed (test *testing.T) {
testString (test,
`
[is5 x:Int]:Bool = switch x
| 5 [return true]
* false
`)
}
func TestSwitchErrBadLiteral (test *testing.T) {
testStringErr (test,
"cannot use array literal as Int", 4, 4,
`
[switch x:Int]:Int = switch x
| 0 5
| (1) 4
| 2 3
* 0
`)
}
func TestSwitchErrNotConstant (test *testing.T) {
testStringErr (test,
"y cannot represent a constant integer", 7, 4,
`
[switch x:Int]:Int = {
y:Int = 1
switch x
| 0 5
| y 4
| 2 3
* 0
}
`)
}
func TestSwitchErrDuplicate (test *testing.T) {
testStringErr (test,
"65 already listed in match at stream0.fspl:6:2", 7, 4,
`
[switch x:I8]:Int = switch x
| 0 5
| 1 4
| 2 3
| 65 2
| 'A' 1
* 0
`)
}
func TestIfElseReturnValueUsed (test *testing.T) {
testString (test,
`

View File

@ -46,6 +46,8 @@ func (this *Tree) analyzeExpression (
return this.analyzeIfElse(into, mode, expression)
case *entity.Match:
return this.analyzeMatch(into, mode, expression)
case *entity.Switch:
return this.analyzeSwitch(into, mode, expression)
case *entity.Loop:
return this.analyzeLoop(into, mode, expression)
case *entity.For:

View File

@ -759,7 +759,21 @@ func (this *Tree) analyzeMatch (
match.CaseMap[hash] = cas
}
if into != nil && len(match.Cases) < len(rawUnion.Allowed) {
if match.Default != nil {
this.pushScope(match.Default)
expression, err := this.analyzeExpression (
into, mode,
match.Default.Expression)
this.popScope()
if err != nil { return nil, err }
match.Default.Expression = expression
}
allCasesCovered :=
len(match.Cases) == len(rawUnion.Allowed) ||
match.Default != nil
if into != nil && !allCasesCovered {
return nil, errors.Errorf (
match.Position(),
"match does not cover all types within %v",
@ -772,7 +786,7 @@ func (this *Tree) analyzeMatch (
}
func (this *Tree) assembleMatchMap (match *entity.Match) (*entity.Match, error) {
match.CaseMap = make(map[entity.Hash] *entity.Case)
match.CaseMap = make(map[entity.Hash] *entity.MatchCase)
match.CaseOrder = make([]entity.Hash, len(match.Cases))
for index, cas := range match.Cases {
hash := cas.Declaration.Ty.Hash()
@ -789,6 +803,104 @@ func (this *Tree) assembleMatchMap (match *entity.Match) (*entity.Match, error)
return match, nil
}
func (this *Tree) analyzeSwitch (
into entity.Type,
mode strictness,
switc *entity.Switch,
) (
entity.Expression,
error,
) {
value, err := this.analyzeExpression(nil, strict, switc.Value)
if err != nil { return nil, err }
switc.Value = value
width, integerOk := integerWidth(value.Type())
wordOk := isWord(value.Type())
if !integerOk && !wordOk {
return nil, errors.Errorf (
value.Position(), "cannot switch on type %v",
value.Type())
}
switc.CaseOrder = make([]int64, len(switc.Cases))
switc.CaseMap = make(map[int64] *entity.SwitchCase)
for index, cas := range switc.Cases {
this.pushScope(cas)
key, err := this.analyzeExpression (
value.Type(), strict,
cas.Key)
if err != nil { this.popScope(); return nil, err }
cas.Key = key
expression, err := this.analyzeExpression (
into, mode,
cas.Expression)
if err != nil { this.popScope(); return nil, err }
cas.Expression = expression
this.popScope()
var keyInt int64
switch key := key.(type) {
case *entity.LiteralInt:
keyInt = int64(key.Value)
case *entity.LiteralString:
if !integerOk {
return nil, errors.Errorf (
key.Position(),
"cannot use string literal when " +
"switching on %v",
value.Type())
}
switch {
case width >= 32:
keyInt = int64(key.ValueUTF32[0])
case width >= 16:
keyInt = int64(key.ValueUTF16[0])
default:
keyInt = int64(key.ValueUTF8[0])
}
default:
return nil, errors.Errorf (
key.Position(),
"%v cannot represent a constant integer",
key)
}
if previous, exists := switc.CaseMap[keyInt]; exists {
return nil, errors.Errorf (
cas.Key.Position(),
"%v already listed in match at %v",
keyInt, previous.Position())
}
switc.Cases[index] = cas
switc.CaseMap[keyInt] = cas
switc.CaseOrder[index] = keyInt
}
if switc.Default != nil {
this.pushScope(switc.Default)
expression, err := this.analyzeExpression (
into, mode,
switc.Default.Expression)
this.popScope()
if err != nil { return nil, err }
switc.Default.Expression = expression
}
if into != nil && switc.Default == nil {
return nil, errors.Errorf (
switc.Position(),
"switch must have a default case")
}
switc.Ty = into
return switc, nil
}
func (this *Tree) analyzeLoop (
into entity.Type,
mode strictness,

View File

@ -82,6 +82,23 @@ testUnit (test,
0,
)}
func TestMatchDefaultPrint (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/match-default-print", dependencies,
"", "something else\n",
0,
)}
func TestSwitchExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/switch-exit-code", nil,
"", "",
4,
)}
func TestReturnAssign (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),

View File

@ -0,0 +1,2 @@
'4f3d6ccb-c233-4648-abd2-72e3f9a82efd'
+ 'io'

View File

@ -0,0 +1,11 @@
[main]: I32 'main' = {
x:UInt = 7
[matchToInt x]
0
}
U: (| Int F64 UInt)
[matchToInt u:U] = match u
| u:Int io::[println 'Int']
| u:F64 io::[println 'F64']
* io::[println 'something else']

View File

@ -0,0 +1 @@
'433ed4ee-be14-4d0f-baef-a13919ec3b6c'

View File

@ -0,0 +1,10 @@
[main]: I32 'main' = {
x:I32 = 1
[switch x]
}
[switch x:I32]:I32 = switch x
| 0 5
| 1 4
| 2 3
* 0

View File

@ -266,8 +266,15 @@ 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.
possible types in the union must be accounted for, or it must have a default
case. It may be assigned to any type that satisfies the assignment rules of its
first case.
### Switch
Switch is a control flow branching expression that executes one of several case
expressions depending on the value of the input. It accepts any pointer or
integer type. If the value of the switch expression is used, a default case must
be present. 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
@ -366,7 +373,8 @@ this without hand-writing a parser.
<ifelse> -> "if" <expression>
"then" <expression>
["else" <expression>]
<match> -> "match" <expression> <case>*
<match> -> "match" <expression> <matchCase>* [<defaultCase>]
<switch> -> "switch" <expression> <switchCase>* [<defaultCase>]
<loop> -> "loop" <expression>
<for> -> "for" <expression> [<expression>] in <expression> <expression>
<break> -> "[" "break" [<expression>] "]"
@ -384,7 +392,9 @@ this without hand-writing a parser.
<booleanLiteral> -> "true" | "false"
<member> -> <identifier> ":" <expression>
<case> -> "|" <declaration> <expression>
<matchCase> -> "|" <declaration> <expression>
<switchCase> -> "|" <expression> <expression>
<defaultCase> -> "*" <expression>
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
<identifier> -> /[a-z][A-Za-z]*/
<typeIdentifier> -> /[A-Z][A-Za-z]*/

View File

@ -414,12 +414,13 @@ type Match struct {
// Syntax
Pos errors.Position
Value Expression
Cases []*Case
Cases []*MatchCase
Default *DefaultCase
// Semantics
Ty Type
CaseOrder []Hash
CaseMap map[Hash] *Case
CaseMap map[Hash] *MatchCase
}
func (*Match) expression(){}
func (this *Match) Position () errors.Position { return this.Pos }
@ -436,6 +437,48 @@ func (this *Match) String () string {
for _, cas := range this.Cases {
out += fmt.Sprint(" ", cas)
}
if this.Default != nil {
out += fmt.Sprint(" ", this.Default)
}
return out
}
var _ Expression = &Switch { }
// Switch is a control flow branching expression that executes one of several
// case expressions depending on the value of the input. It accepts any pointer
// or integer type. If the value of the switch expression is used, a default
// case must be present. It may be assigned to any type that satisfies the
// assignment rules of its first case.
type Switch struct {
// Syntax
Pos errors.Position
Value Expression
Cases []*SwitchCase
Default *DefaultCase
// Semantics
Ty Type
CaseOrder []int64
CaseMap map[int64] *SwitchCase
}
func (*Switch) expression(){}
func (this *Switch) Position () errors.Position { return this.Pos }
func (this *Switch) Type () Type { return this.Ty }
func (this *Switch) HasExplicitType () bool {
if len(this.Cases) == 0 {
return true
} else {
return this.Cases[0].HasExplicitType()
}
}
func (this *Switch) String () string {
out := fmt.Sprint("switch ", this.Value)
for _, cas := range this.Cases {
out += fmt.Sprint(" ", cas)
}
if this.Default != nil {
out += fmt.Sprint(" ", this.Default)
}
return out
}

View File

@ -101,18 +101,41 @@ func (this *Member) String () string {
return fmt.Sprint(this.Name, ":", this.Value)
}
// Case represents a match case.
type Case struct {
// MatchCase represents a case in a match expression.
type MatchCase struct {
Pos errors.Position
Declaration *Declaration
Expression
Scope
}
func (this *Case) Position () errors.Position { return this.Pos }
func (this *Case) String () string {
func (this *MatchCase) Position () errors.Position { return this.Pos }
func (this *MatchCase) String () string {
return fmt.Sprint("| ", this.Declaration, this.Expression)
}
// SwitchCase represents a case in a switch expression.
type SwitchCase struct {
Pos errors.Position
Key Expression
Expression
Scope
}
func (this *SwitchCase) Position () errors.Position { return this.Pos }
func (this *SwitchCase) String () string {
return fmt.Sprint("| ", this.Key, this.Expression)
}
// DefaultCase represents the default case in a switch or match expression.
type DefaultCase struct {
Pos errors.Position
Expression
Scope
}
func (this *DefaultCase) Position () errors.Position { return this.Pos }
func (this *DefaultCase) String () string {
return fmt.Sprint("* ", this.Expression)
}
// Operator determines which operation to apply to a value in an operation
// expression.
type Operator int; const (

View File

@ -340,6 +340,145 @@ U: (| Int F64 UInt)
`)
}
func TestMatchDefault (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 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
%9 = alloca [3 x i8]
%10 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 0
%11 = load i64, ptr %10
switch i64 %11, label %31 [
i64 7620767046192759206, label %13
i64 9186060094042213285, label %22
]
12:
ret void
13:
%14 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%15 = load i64, ptr %14
store i64 %15, ptr %2
%16 = getelementptr [3 x i8], ptr %4, i32 0, i32 0
store i8 73, ptr %16
%17 = getelementptr [3 x i8], ptr %4, i32 0, i32 1
store i8 110, ptr %17
%18 = getelementptr [3 x i8], ptr %4, i32 0, i32 2
store i8 116, ptr %18
%19 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3, i32 0, i32 0
store ptr %4, ptr %19
%20 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %20
%21 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %21)
br label %12
22:
%23 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %1, i32 0, i32 1
%24 = load double, ptr %23
store double %24, ptr %5
%25 = getelementptr [3 x i8], ptr %7, i32 0, i32 0
store i8 70, ptr %25
%26 = getelementptr [3 x i8], ptr %7, i32 0, i32 1
store i8 54, ptr %26
%27 = getelementptr [3 x i8], ptr %7, i32 0, i32 2
store i8 52, ptr %27
%28 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %6, i32 0, i32 0
store ptr %7, ptr %28
%29 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %6, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %29
%30 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %6
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %30)
br label %12
31:
%32 = getelementptr [3 x i8], ptr %9, i32 0, i32 0
store i8 105, ptr %32
%33 = getelementptr [3 x i8], ptr %9, i32 0, i32 1
store i8 100, ptr %33
%34 = getelementptr [3 x i8], ptr %9, i32 0, i32 2
store i8 107, ptr %34
%35 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %8, i32 0, i32 0
store ptr %9, ptr %35
%36 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %8, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %36
%37 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %8
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %37)
br label %12
}
declare void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %str)
`,
`
U: (| Int F64 UInt)
[print str:String]
[matchToInt u:U] = match u
| u:Int [print 'Int']
| u:F64 [print 'F64']
* [print 'idk']
`)
}
func TestSwitchReturn (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::is5"(i64 %x) {
0:
%1 = alloca i64
store i64 %x, ptr %1
%2 = load i64, ptr %1
switch i64 %2, label %3 [
i64 5, label %4
]
3:
ret i1 false
4:
ret i1 true
}
`,
`
[is5 x:Int]:Bool = {
switch x | 5 [return true]
false
}
`)
}
func TestSwitchReturnValueUsed (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1
define %"AAAAAAAAAAAAAAAAAAAAAA==::Bool" @"0zNZN147MN2wzMAQ6NS2dQ==::is5"(i64 %x) {
0:
%1 = alloca i64
store i64 %x, ptr %1
%2 = load i64, ptr %1
switch i64 %2, label %6 [
i64 5, label %5
]
3:
%4 = phi i1 [ false, %6 ]
ret i1 %4
5:
ret i1 true
6:
br label %3
}
`,
`
[is5 x:Int]:Bool = switch x
| 5 [return true]
* false
`)
}
func TestIfElseReturnValueUsed (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i1

View File

@ -95,6 +95,8 @@ func (this *generator) generateExpressionAny (expression entity.Expression) (reg
return this.generateIfElse(expression, resultModeAny)
case *entity.Match:
return this.generateMatch(expression, resultModeAny)
case *entity.Switch:
return this.generateSwitch(expression, resultModeAny)
case *entity.Loop:
return this.generateLoop(expression, resultModeAny)
case *entity.For:
@ -160,6 +162,9 @@ func (this *generator) generateExpressionVal (expression entity.Expression) (llv
case *entity.Match:
val, _, err := this.generateMatch(expression, resultModeVal)
return val, err
case *entity.Switch:
val, _, err := this.generateSwitch(expression, resultModeVal)
return val, err
case *entity.Loop:
val, _, err := this.generateLoop(expression, resultModeVal)
return val, err
@ -235,6 +240,9 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
case *entity.Match:
loc, _, err := this.generateMatch(expression, resultModeLoc)
return loc, err
case *entity.Switch:
loc, _, err := this.generateSwitch(expression, resultModeLoc)
return loc, err
case *entity.Loop:
loc, _, err := this.generateLoop(expression, resultModeLoc)
return loc, err

View File

@ -122,7 +122,6 @@ 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())
@ -134,6 +133,31 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
irCases := make([]*llvm.Case, len(match.Cases))
irIncomings := []*llvm.Incoming { }
loc := false
first := true
generateCaseExpression := func (expression entity.Expression) error {
thisMode := mode
if !first && mode != resultModeAny {
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
}
result, thisLoc, err := this.generateExpression(expression, thisMode)
if err != nil { return err }
if first { loc = thisLoc }
if !this.blockManager.Terminated() {
if expression != nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: result,
Predecessor: this.blockManager.Block,
})
}
this.blockManager.NewBr(exitBlock)
}
return nil
}
// generate type cases
for index, cas := range match.Cases {
// set up ir case
caseBlock := this.blockManager.newBlock()
@ -151,30 +175,27 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
this.blockManager.addDeclaration(cas.Declaration, data)
// generate case expression
thisMode := mode
if index != 0 && mode != resultModeAny {
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
}
caseExpression, thisLoc, err := this.generateExpression(cas.Expression, thisMode)
err = generateCaseExpression(cas.Expression)
if err != nil { return nil, false, err }
}
// generate default case
var defaultBlock llvm.Value
if match.Default != nil {
defaultBlock = this.blockManager.newBlock()
err = generateCaseExpression(match.Default.Expression)
if err != nil { return nil, false, err }
if index == 0 { loc = thisLoc }
if !this.blockManager.Terminated() {
if caseExpression != nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: caseExpression,
Predecessor: this.blockManager.Block,
})
}
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...)
if defaultBlock == nil {
this.blockManager.NewSwitch(hash, exitBlock, irCases...)
} else {
this.blockManager.NewSwitch(hash, defaultBlock, irCases...)
}
// discard/obtain results
this.blockManager.Block = exitBlock
@ -183,10 +204,94 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
} 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,
})
if defaultBlock == nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: llvm.NewConstZeroInitializer(irType),
Predecessor: previousBlock,
})
}
return this.blockManager.NewPhi(irIncomings...), loc, nil
}
}
func (this *generator) generateSwitch (match *entity.Switch, mode resultMode) (llvm.Value, bool, error) {
irValue, err := this.generateExpressionVal(match.Value)
if err != nil { return nil, false, err }
irValueType, err := this.generateType(match.Value.Type())
if err != nil { return nil, false, err }
previousBlock := this.blockManager.Block
exitBlock := this.blockManager.newBlock()
irCases := make([]*llvm.Case, len(match.Cases))
irIncomings := []*llvm.Incoming { }
loc := false
first := true
generateCaseExpression := func (expression entity.Expression) error {
thisMode := mode
if !first && mode != resultModeAny {
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
}
result, thisLoc, err := this.generateExpression(expression, thisMode)
if err != nil { return err }
if first { loc = thisLoc }
if !this.blockManager.Terminated() {
if expression != nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: result,
Predecessor: this.blockManager.Block,
})
}
this.blockManager.NewBr(exitBlock)
}
return nil
}
// generate value cases
for index, cas := range match.Cases {
caseBlock := this.blockManager.newBlock()
key := match.CaseOrder[index]
irCases[index] = &llvm.Case {
X: llvm.NewConstInt (
llvm.ReduceToBase(irValueType).(*llvm.TypeInt),
key),
Target: caseBlock,
}
err = generateCaseExpression(cas.Expression)
if err != nil { return nil, false, err }
}
// generate default case
var defaultBlock llvm.Value
if match.Default != nil {
defaultBlock = this.blockManager.newBlock()
err = generateCaseExpression(match.Default.Expression)
if err != nil { return nil, false, err }
}
// create switch branch
this.blockManager.Block = previousBlock
if defaultBlock == nil {
this.blockManager.NewSwitch(irValue, exitBlock, irCases...)
} else {
this.blockManager.NewSwitch(irValue, defaultBlock, 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 }
if defaultBlock == nil {
irIncomings = append(irIncomings, &llvm.Incoming {
X: llvm.NewConstZeroInitializer(irType),
Predecessor: previousBlock,
})
}
return this.blockManager.NewPhi(irIncomings...), loc, nil
}
}

View File

@ -70,6 +70,7 @@ expression, the parser follows this decision tree to determine what to parse:
| | 'nil' =LiteralNil
| | 'if' =IfElse
| | 'match' =Match
| | 'switch' =Switch
| | 'loop' =Loop
| | +Colon =Declaration
| | +DoubleColon =Call

View File

@ -117,6 +117,7 @@ func (this *treeParser) parseExpressionRootIdent () (entity.Expression, error) {
case "nil": return this.parseLiteralNil()
case "if": return this.parseIfElse()
case "match": return this.parseMatch()
case "switch": return this.parseSwitch()
case "loop": return this.parseLoop()
case "for": return this.parseFor()
default:
@ -580,18 +581,24 @@ func (this *treeParser) parseMatch () (*entity.Match, error) {
if err != nil { return nil, err }
for this.Is(lexer.Symbol) && this.ValueIs("|") {
cas, err := this.parseCase()
cas, err := this.parseMatchCase()
if err != nil { return nil, err }
match.Cases = append(match.Cases, cas)
}
if this.Is(lexer.Star) {
defaul, err := this.parseDefaultCase()
if err != nil { return nil, err }
match.Default = defaul
}
return match, nil
}
func (this *treeParser) parseCase () (*entity.Case, error) {
func (this *treeParser) parseMatchCase () (*entity.MatchCase, error) {
err := this.ExpectValue(lexer.Symbol, "|")
if err != nil { return nil, err }
cas := &entity.Case {
cas := &entity.MatchCase {
Pos: this.Pos(),
}
@ -604,6 +611,63 @@ func (this *treeParser) parseCase () (*entity.Case, error) {
return cas, nil
}
func (this *treeParser) parseSwitch () (*entity.Switch, error) {
err := this.ExpectValue(lexer.Ident, "switch")
if err != nil { return nil, err }
switc := &entity.Switch {
Pos: this.Pos(),
}
err = this.ExpectNextDesc(descriptionExpression, startTokensExpression...)
if err != nil { return nil, err }
switc.Value, err = this.parseExpression()
if err != nil { return nil, err }
for this.Is(lexer.Symbol) && this.ValueIs("|") {
cas, err := this.parseSwitchCase()
if err != nil { return nil, err }
switc.Cases = append(switc.Cases, cas)
}
if this.Is(lexer.Star) {
defaul, err := this.parseDefaultCase()
if err != nil { return nil, err }
switc.Default = defaul
}
return switc, nil
}
func (this *treeParser) parseSwitchCase () (*entity.SwitchCase, error) {
err := this.ExpectValue(lexer.Symbol, "|")
if err != nil { return nil, err }
cas := &entity.SwitchCase {
Pos: this.Pos(),
}
this.Next()
cas.Key, err = this.parseExpression()
if err != nil { return nil, err }
cas.Expression, err = this.parseExpression()
if err != nil { return nil, err }
return cas, nil
}
func (this *treeParser) parseDefaultCase () (*entity.DefaultCase, error) {
err := this.Expect(lexer.Star)
if err != nil { return nil, err }
cas := &entity.DefaultCase {
Pos: this.Pos(),
}
this.Next()
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

@ -74,7 +74,8 @@ testString (test,
- [loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}
- [for s:String] = for i:Index e:Byte in str if [> i 3] then [break e]
- [for s:String] = for e:Byte in s [break e]
- [matchToInt u:(| Int F64)]:Int = match u | u:Int u | u:F64 [~ Int u]`,
- [matchToInt u:(| Int F64)]:Int = match u | u:Int u | u:F64 [~ Int u] * 0
- [switch x:Int] = switch x | 0 5 | 1 4 | 2 3 * 0`,
// input
`
[var] = sdfdf
@ -127,6 +128,12 @@ testString (test,
[matchToInt u:(| Int F64)]:Int = match u
| u:Int u
| u:F64 [~Int u]
* 0
[switch x:Int] = switch x
| 0 5
| 1 4
| 2 3
* 0
`)
}