implement-range-loops #68

Merged
sashakoshka merged 23 commits from implement-range-loops into main 2024-03-24 18:03:54 -06:00
29 changed files with 661 additions and 74 deletions

View File

@ -109,3 +109,47 @@ testString (test,
} }
`) `)
} }
func TestFor (test *testing.T) {
testString (test,
`
[f str:String] = for c:Byte in str { }
[g str:String] = for i:Index c:Byte in str { }
`)}
func TestForBreak (test *testing.T) {
testString (test,
`
[f str:String]:Byte = for c:Byte in str [break c]
`)}
func TestForBreakBadType (test *testing.T) {
testStringErr (test,
"expected Byte", 2, 56,
`
[f str:String]:Byte = for i:Index c:Byte in str [break i]
`)}
func TestForErrBadIndex (test *testing.T) {
testStringErr (test,
"expected Index", 2, 22,
`
[f str:String] = for i:Int c:Byte in str { }
`)}
func TestForErrBadOver (test *testing.T) {
testStringErr (test,
"cannot use U8 as Int", 2, 31,
`
[f str:String] = for c:Int in str { }
`)}
func TestForErrDeclarationScoped (test *testing.T) {
testStringErr (test,
"no variable named c", 4, 2,
`
[f str:String] = {
for c:Byte in str { }
c = 'a'
}
`)}

View File

@ -48,6 +48,8 @@ func (this *Tree) analyzeExpression (
return this.analyzeMatch(into, mode, expression) return this.analyzeMatch(into, mode, expression)
case *entity.Loop: case *entity.Loop:
return this.analyzeLoop(into, mode, expression) return this.analyzeLoop(into, mode, expression)
case *entity.For:
return this.analyzeFor(into, mode, expression)
case *entity.Break: case *entity.Break:
return this.analyzeBreak(into, mode, expression) return this.analyzeBreak(into, mode, expression)
case *entity.Return: case *entity.Return:

View File

@ -808,6 +808,52 @@ func (this *Tree) analyzeLoop (
return loop, nil return loop, nil
} }
func (this *Tree) analyzeFor (
into entity.Type,
mode strictness,
loop *entity.For,
) (
entity.Expression,
error,
) {
loop.Ty = into
this.pushScope(loop)
defer this.popScope()
// index
if loop.Index != nil {
index, err := this.analyzeDeclaration (
builtinType("Index"),
strict, loop.Index)
if err != nil { return nil, err }
loop.Index = index.(*entity.Declaration)
}
// element
element, err := this.analyzeDeclaration(nil, strict, loop.Element)
if err != nil { return nil, err }
loop.Element = element.(*entity.Declaration)
// over
over, err := this.analyzeExpression (
&entity.TypeSlice {
Pos: loop.Position(),
Element: element.Type(),
}, weak, loop.Over)
if err != nil { return nil, err }
loop.Over = over
this.pushLoop(loop)
defer this.popLoop()
body, err := this.analyzeExpression(nil, strict, loop.Body)
if err != nil { return nil, err }
loop.Body = body
return loop, nil
}
func (this *Tree) analyzeBreak ( func (this *Tree) analyzeBreak (
into entity.Type, into entity.Type,
mode strictness, mode strictness,

View File

@ -16,7 +16,7 @@ func (this *scopeContextManager) popScopeContext () {
*this = (*this)[:len(*this) - 1] *this = (*this)[:len(*this) - 1]
} }
func (this *scopeContextManager) pushLoop (loop *entity.Loop) { func (this *scopeContextManager) pushLoop (loop entity.Breakable) {
this.assertPopulated() this.assertPopulated()
(*this)[len(*this) - 1].pushLoop(loop) (*this)[len(*this) - 1].pushLoop(loop)
} }
@ -26,7 +26,7 @@ func (this *scopeContextManager) popLoop () {
(*this)[len(*this) - 1].popLoop() (*this)[len(*this) - 1].popLoop()
} }
func (this *scopeContextManager) topLoop () (*entity.Loop, bool) { func (this *scopeContextManager) topLoop () (entity.Breakable, bool) {
this.assertPopulated() this.assertPopulated()
return (*this)[len(*this) - 1].topLoop() return (*this)[len(*this) - 1].topLoop()
} }
@ -71,11 +71,11 @@ func (this *scopeContextManager) assertPopulated () {
// entities. // entities.
type scopeContext struct { type scopeContext struct {
scopes []entity.Scoped scopes []entity.Scoped
loops []*entity.Loop loops []entity.Breakable
declaration entity.TopLevel declaration entity.TopLevel
} }
func (this *scopeContext) pushLoop (loop *entity.Loop) { func (this *scopeContext) pushLoop (loop entity.Breakable) {
this.loops = append(this.loops, loop) this.loops = append(this.loops, loop)
} }
@ -84,7 +84,7 @@ func (this *scopeContext) popLoop () {
this.loops = this.loops[:len(this.loops) - 1] this.loops = this.loops[:len(this.loops) - 1]
} }
func (this *scopeContext) topLoop () (*entity.Loop, bool) { func (this *scopeContext) topLoop () (entity.Breakable, bool) {
if len(this.loops) < 1 { return nil, false } if len(this.loops) < 1 { return nil, false }
return this.loops[len(this.loops) - 1], true return this.loops[len(this.loops) - 1], true
} }

View File

@ -1,9 +1,11 @@
package compiler package compiler
import "time"
import "io/fs" import "io/fs"
import "embed" import "embed"
import "strings" import "strings"
import "testing" import "testing"
import "context"
import "os/exec" import "os/exec"
import "path/filepath" import "path/filepath"
import "git.tebibyte.media/fspl/fspl/entity" import "git.tebibyte.media/fspl/fspl/entity"
@ -73,9 +75,9 @@ func testUnit (
// instantiate and configure the compiler // instantiate and configure the compiler
compOutputBuilder := new(strings.Builder) compOutputBuilder := new(strings.Builder)
comp := defaultCompiler() comp := defaultCompiler()
defer func () { outputCompilerLog := func () {
test.Log("COMPILER LOG (main unit):\n" + compOutputBuilder.String()) test.Log("COMPILER LOG (main unit):\n" + compOutputBuilder.String())
} () }
comp.Writer = compOutputBuilder comp.Writer = compOutputBuilder
comp.Output = filepath.Join(temp, "output.o") comp.Output = filepath.Join(temp, "output.o")
@ -83,8 +85,10 @@ func testUnit (
// compile to object file // compile to object file
err := comp.CompileUnit(address) err := comp.CompileUnit(address)
if err != nil { if err != nil {
outputCompilerLog()
test.Fatal("compiler returned error:", errors.Format(err)) test.Fatal("compiler returned error:", errors.Format(err))
} }
outputCompilerLog()
// link the object file into an executable // link the object file into an executable
executablePath := filepath.Join(temp, "output") executablePath := filepath.Join(temp, "output")
@ -101,14 +105,21 @@ func testUnit (
test.Fatal("error linking executable:", err) test.Fatal("error linking executable:", err)
} }
// run the executable file and check its output // run the executable file (with timeout) and check its output
executableCommand := exec.Command(executablePath, args...) timeoutDuration := 10 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
defer cancel()
executableCommand := exec.CommandContext(ctx, executablePath, args...)
stdoutBuilder := new(strings.Builder) stdoutBuilder := new(strings.Builder)
executableCommand.Stdin = strings.NewReader(stdin) executableCommand.Stdin = strings.NewReader(stdin)
executableCommand.Stdout = stdoutBuilder executableCommand.Stdout = stdoutBuilder
test.Log("running executable command: ", executableCommand) test.Log("running executable command: ", executableCommand)
err = executableCommand.Run() err = executableCommand.Run()
if ctx.Err() == context.DeadlineExceeded {
test.Errorf("timeout of %v exceeded!", timeoutDuration)
}
// check error, compare exit code // check error, compare exit code
if exitErr, ok := err.(*exec.ExitError); ok { if exitErr, ok := err.(*exec.ExitError); ok {
code := exitErr.ExitCode() code := exitErr.ExitCode()

View File

@ -91,3 +91,54 @@ testUnit (test,
"", "false\n", "", "false\n",
0, 0,
)} )}
func TestLoopBreakExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/loop-break-exit-code", nil,
"", "",
5,
)}
func TestLoopBreakBranchExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/loop-break-branch-exit-code", nil,
"", "",
2,
)}
func TestForSimple (test *testing.T) {
testUnit (test,
"/test-data/data/for-simple", nil,
"", "",
0,
)}
func TestForStringArray (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/for-string-array", dependencies,
"", "a\nb\nc\na\nb\nc\n",
0,
)}
func TestForStringArrayOnce (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/for-string-array-once", dependencies,
"", "abc\n",
0,
)}
func TestForBreakBranch (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/for-break-branch", dependencies,
"", "iter\niter\n",
0,
)}

View File

@ -0,0 +1,2 @@
'61b23750-b3c5-4cbd-85f7-71322a23f980'
+ 'io'

View File

@ -0,0 +1,8 @@
[main]:I32 'main' = {
arr:3:Int = (* 5 6 7)
for i:Index e:Int in arr {
if [>= i 2] then [break]
io::[println 'iter']
}
0
}

View File

@ -0,0 +1,2 @@
'11371094-76a7-424f-b9ae-14636962073b'
+ 'io'

View File

@ -0,0 +1,5 @@
[main]:I32 'main' = {
str:String = 'a'
for c:Byte in str { }
0
}

View File

@ -0,0 +1,3 @@
'c9024148-f4ec-4d69-84c1-c838f9c68073'
+ 'io'
+ 'cstdio'

View File

@ -0,0 +1,5 @@
[main]:I32 'main' = {
arr:5:String = (* 'abc' 'def' 'ghi')
for e:String in arr { io::[println e] [break] }
0
}

View File

@ -0,0 +1,2 @@
'cfb45339-4c4d-4519-9578-3abf0c698867'
+ 'io'

View File

@ -0,0 +1,10 @@
[main]:I32 'main' = {
; array
arr:3:String = (* 'a' 'b' 'c')
for e:String in arr io::[println e]
; slice
slice:*:String = arr
for e:String in slice io::[println e]
0
}

View File

@ -0,0 +1 @@
'e8962449-6214-4622-afcf-f6b58046fab2'

View File

@ -0,0 +1,10 @@
[main]:I32 'main' = {
y:I32 = 6
loop {
if [< y 3]
then [break y]
else {
y = [-- y]
}
}
}

View File

@ -0,0 +1 @@
'779e632d-88a6-46c4-bfa2-7db4fc919f07'

View File

@ -0,0 +1,3 @@
[main]:I32 'main' = loop {
[break 5]
}

View File

@ -276,6 +276,12 @@ statement. The result of the loop may be assigned to any type that satisfies the
assignment rules of all of its break statements. Loops may be nested, and break assignment rules of all of its break statements. Loops may be nested, and break
statements only apply to the closest containing loop. The value of the loop's statements only apply to the closest containing loop. The value of the loop's
expression is never used. expression is never used.
### For
For is a special kind of loop that evaluates an expression for each element of
an array or slice. It accepts an index declaration and an element declaration,
which are scoped to the loop's body and are set to the index of the current
element and the element itself respectively at the beginning of each iteration.
The assignment rules of a for statement are identical to that of a normal loop.
### Break ### Break
Break allows breaking out of loops. It may be assigned to anything, but the Break allows breaking out of loops. It may be assigned to anything, but the
assignment will have no effect as execution of the code after and surrounding it assignment will have no effect as execution of the code after and surrounding it
@ -362,6 +368,7 @@ this without hand-writing a parser.
["else" <expression>] ["else" <expression>]
<match> -> "match" <expression> <case>* <match> -> "match" <expression> <case>*
<loop> -> "loop" <expression> <loop> -> "loop" <expression>
<for> -> "for" <expression> [<expression>] in <expression> <expression>
<break> -> "[" "break" [<expression>] "]" <break> -> "[" "break" [<expression>] "]"
<return> -> "[" "return" [<expression>] "]" <return> -> "[" "return" [<expression>] "]"
<assignment> -> <expression> "=" <expression> <assignment> -> <expression> "=" <expression>

View File

@ -439,7 +439,14 @@ func (this *Match) String () string {
return out return out
} }
// Breakable is any expression that can be halted using a break.
type Breakable interface {
Expression
breakable()
}
var _ Expression = &Loop { } var _ Expression = &Loop { }
var _ Breakable = &Loop { }
// Loop is a control flow expression that repeats an expression until a break // 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 // 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 // if the value of the loop is used. Otherwise, it need not even have a break
@ -456,6 +463,7 @@ type Loop struct {
Ty Type Ty Type
} }
func (*Loop) expression(){} func (*Loop) expression(){}
func (*Loop) breakable(){}
func (this *Loop) Position () errors.Position { return this.Pos } func (this *Loop) Position () errors.Position { return this.Pos }
func (this *Loop) Type () Type { return this.Ty } func (this *Loop) Type () Type { return this.Ty }
func (this *Loop) HasExplicitType () bool { func (this *Loop) HasExplicitType () bool {
@ -467,6 +475,44 @@ func (this *Loop) String () string {
return fmt.Sprint("loop ", this.Body) return fmt.Sprint("loop ", this.Body)
} }
var _ Expression = &For { }
var _ Breakable = &For { }
// For is a special kind of loop that evaluates an expression for each element
// of an array or slice. It accepts an index declaration and an element
// declaration, which are scoped to the loop's body and are set to the index of
// the current element and the element itself respectively at the beginning of
// each iteration. The assignment rules of a for statement are identical to that
// of a normal loop.
type For struct {
// Syntax
Pos errors.Position
Index *Declaration
Element *Declaration
Over Expression
Body Expression
// Semantics
Ty Type
Scope
}
func (*For) expression(){}
func (*For) breakable(){}
func (this *For) Position () errors.Position { return this.Pos }
func (this *For) Type () Type { return this.Ty }
func (this *For) HasExplicitType () bool {
// this is as accurate as possible without doing a full analysis of the
// loop
return false
}
func (this *For) String () string {
out := "for"
if this.Index != nil {
out += " " + this.Index.String()
}
out += fmt.Sprintf(" %v in %v %v", this.Element, this.Over, this.Body)
return out
}
var _ Expression = &Break { } var _ Expression = &Break { }
// Break allows breaking out of loops. It has no value and may not be assigned // Break allows breaking out of loops. It has no value and may not be assigned
// to anything. It is never a valid location expression. // to anything. It is never a valid location expression.
@ -476,7 +522,7 @@ type Break struct {
Value Expression Value Expression
// Semantics // Semantics
Loop *Loop Loop Breakable
} }
func (*Break) expression(){} func (*Break) expression(){}
func (this *Break) Position () errors.Position { return this.Pos } func (this *Break) Position () errors.Position { return this.Pos }

View File

@ -165,7 +165,12 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
source, err := this.generateExpressionVal(source) source, err := this.generateExpressionVal(source)
if err != nil { return nil, err } if err != nil { return nil, err }
this.blockManager.NewStore(source, destDataFieldLoc) this.blockManager.NewStore(source, destDataFieldLoc)
if destinationSpecified {
return nil, nil
} else {
return this.blockManager.NewLoad(irDestType, irDestLoc), nil return this.blockManager.NewLoad(irDestType, irDestLoc), nil
}
// conversion from any type to pointer // conversion from any type to pointer
case *entity.TypePointer: case *entity.TypePointer:
@ -192,6 +197,8 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) { switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) {
// conversion from array to slice // conversion from array to slice
case *entity.TypeArray: case *entity.TypeArray:
irIndexType, err := this.generateTypeIndex()
if err != nil { return nil, err }
array, err := this.generateExpressionLoc(source) array, err := this.generateExpressionLoc(source)
if err != nil { return nil, err } if err != nil { return nil, err }
irDestType, err := this.generateType(destType) irDestType, err := this.generateType(destType)
@ -204,10 +211,12 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
destLengthField := this.getSliceLengthFieldLoc(irDestLoc, irDestType) destLengthField := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
this.blockManager.NewStore(array, destDataField) this.blockManager.NewStore(array, destDataField)
this.blockManager.NewStore ( this.blockManager.NewStore (
llvm.NewConstInt(llvm.I32, int64(sourceTypeBase.Length)), llvm.NewConstInt(irIndexType, int64(sourceTypeBase.Length)),
destLengthField) destLengthField)
if !destinationSpecified { if destinationSpecified {
return nil, nil
} else {
return this.blockManager.NewLoad(irDestType, irDestLoc), nil return this.blockManager.NewLoad(irDestType, irDestLoc), nil
} }
} }

View File

@ -6,10 +6,40 @@ import "git.tebibyte.media/fspl/fspl/llvm"
import "git.tebibyte.media/fspl/fspl/entity" import "git.tebibyte.media/fspl/fspl/entity"
type loopEntry struct { type loopEntry struct {
value llvm.Value breakBlocks []*llvm.Incoming // incoming value/block pairs of breaks
stub *llvm.Block mode resultMode // result mode for break statements
mode resultMode loc bool // for when mode is resultModeAny
loc bool }
// tieUp branches all breakBlocks to exit.
func (this *loopEntry) tieUp (exit *llvm.Block) {
for _, incoming := range this.breakBlocks {
predecessor := incoming.Predecessor.(*llvm.Block)
predecessor.SetTerminator(&llvm.TerminatorBr {
Target: exit,
})
}
}
// tieUpPhi branches all breakBlocks to exit, creates a new phi in exit, and
// returns the phi.
func (this *loopEntry) tieUpPhi (ty llvm.Type, exit *llvm.Block, extraIncomings... *llvm.Incoming) llvm.Value {
for _, incoming := range this.breakBlocks {
predecessor := incoming.Predecessor.(*llvm.Block)
predecessor.SetTerminator(&llvm.TerminatorBr {
Target: exit,
})
}
totalIncomings := make([]*llvm.Incoming, len(this.breakBlocks))
copy(totalIncomings, this.breakBlocks)
totalIncomings = append(totalIncomings, extraIncomings...)
if len(totalIncomings) == 0 {
return llvm.NewConstZeroInitializer(ty)
} else {
return exit.NewPhi(totalIncomings...)
}
} }
type blockManager struct { type blockManager struct {

View File

@ -76,7 +76,7 @@ testString (test,
`) `)
} }
func TestLoop (test *testing.T) { func TestLoopSimple (test *testing.T) {
testString (test, testString (test,
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() { `define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
0: 0:
@ -105,7 +105,8 @@ testString (test,
2: 2:
br label %3 br label %3
3: 3:
store i64 5, ptr %1 %4 = phi i64 [ 5, %2 ]
store i64 %4, ptr %1
ret void ret void
} }
`, `,
@ -128,19 +129,20 @@ testString (test,
2: 2:
%3 = load i64, ptr %1 %3 = load i64, ptr %1
%4 = icmp slt i64 %3, 3 %4 = icmp slt i64 %3, 3
br i1 %4, label %5, label %8 br i1 %4, label %7, label %10
5: 5:
%6 = load i64, ptr %1 %6 = phi i64 [ %8, %7 ]
br label %11
7:
br label %2
8:
%9 = load i64, ptr %1
%10 = sub i64 %9, 1
store i64 %10, ptr %1
br label %7
11:
ret i64 %6 ret i64 %6
7:
%8 = load i64, ptr %1
br label %5
9:
br label %2
10:
%11 = load i64, ptr %1
%12 = sub i64 %11, 1
store i64 %12, ptr %1
br label %9
} }
`, `,
` `
@ -375,18 +377,19 @@ testString (test,
3: 3:
%4 = load i64, ptr %1 %4 = load i64, ptr %1
%5 = icmp eq i64 %4, 5 %5 = icmp eq i64 %4, 5
br i1 %5, label %6, label %9 br i1 %5, label %8, label %11
6: 6:
br label %11 %7 = phi i64 [ 0, %8 ]
7: ret i64 %7
%8 = phi i64 [ %10, %9 ] 8:
store i64 %8, ptr %2 br label %6
br label %3
9: 9:
%10 = load i64, ptr %1 %10 = phi i64 [ %12, %11 ]
br label %7 store i64 %10, ptr %2
br label %3
11: 11:
ret i64 0 %12 = load i64, ptr %1
br label %9
} }
`, `,
` `
@ -397,3 +400,114 @@ testString (test,
} }
`) `)
} }
func TestFor (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
define void @"0zNZN147MN2wzMAQ6NS2dQ==::f"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %str) {
0:
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %str, ptr %1
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Index"
%3 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
br label %4
4:
%5 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 1
%6 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %5
%7 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%8 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %7, %6
br i1 %8, label %9, label %18
9:
%10 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 0
%11 = load ptr, ptr %10
%12 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%13 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %11, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %12
%14 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %13
store %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %14, ptr %3
%15 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %3
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %15)
%16 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%17 = add %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %16, 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %17, ptr %2
br label %4
18:
ret void
}
declare void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %x)
define void @"0zNZN147MN2wzMAQ6NS2dQ==::g"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %str) {
0:
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %str, ptr %1
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Index"
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" zeroinitializer, ptr %2
%3 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
br label %4
4:
%5 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 1
%6 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %5
%7 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%8 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %7, %6
br i1 %8, label %9, label %18
9:
%10 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 0
%11 = load ptr, ptr %10
%12 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%13 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %11, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %12
%14 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %13
store %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %14, ptr %3
%15 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %3
call void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %15)
%16 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%17 = add %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %16, 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %17, ptr %2
br label %4
18:
ret void
}
`,
`
[print x:Byte]
[f str:String] = for c:Byte in str { [print c] }
[g str:String] = for i:Index c:Byte in str [print c]
`)}
func TestForBreak (test *testing.T) {
testString (test,
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
define %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" @"0zNZN147MN2wzMAQ6NS2dQ==::f"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %str) {
0:
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %str, ptr %1
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Index"
%3 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
br label %4
4:
%5 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 1
%6 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %5
%7 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%8 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %7, %6
br i1 %8, label %9, label %18
9:
%10 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 0
%11 = load ptr, ptr %10
%12 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%13 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %11, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %12
%14 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %13
store %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %14, ptr %3
%15 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %3
%16 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %2
%17 = add %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %16, 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %17, ptr %2
br label %18
18:
%19 = phi %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" [ %15, %9 ], [ zeroinitializer, %4 ]
ret %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %19
}
`,
`
[f str:String]:Byte = for c:Byte in str [break c]
`)}

View File

@ -97,6 +97,8 @@ func (this *generator) generateExpressionAny (expression entity.Expression) (reg
return this.generateMatch(expression, resultModeAny) return this.generateMatch(expression, resultModeAny)
case *entity.Loop: case *entity.Loop:
return this.generateLoop(expression, resultModeAny) return this.generateLoop(expression, resultModeAny)
case *entity.For:
return this.generateFor(expression, resultModeAny)
// we get nothing from these // we get nothing from these
case *entity.Assignment: case *entity.Assignment:
@ -161,6 +163,9 @@ func (this *generator) generateExpressionVal (expression entity.Expression) (llv
case *entity.Loop: case *entity.Loop:
val, _, err := this.generateLoop(expression, resultModeVal) val, _, err := this.generateLoop(expression, resultModeVal)
return val, err return val, err
case *entity.For:
val, _, err := this.generateFor(expression, resultModeVal)
return val, err
case *entity.LiteralInt: case *entity.LiteralInt:
return this.generateLiteralInt(expression) return this.generateLiteralInt(expression)
case *entity.LiteralFloat: case *entity.LiteralFloat:
@ -233,6 +238,9 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
case *entity.Loop: case *entity.Loop:
loc, _, err := this.generateLoop(expression, resultModeLoc) loc, _, err := this.generateLoop(expression, resultModeLoc)
return loc, err return loc, err
case *entity.For:
loc, _, err := this.generateFor(expression, resultModeLoc)
return loc, err
case *entity.LiteralArray: case *entity.LiteralArray:
return this.generateLiteralArrayLoc(expression, nil) return this.generateLiteralArrayLoc(expression, nil)
case *entity.LiteralString: case *entity.LiteralString:

View File

@ -3,7 +3,7 @@ package generator
// import "fmt" // import "fmt"
import "git.tebibyte.media/fspl/fspl/llvm" import "git.tebibyte.media/fspl/fspl/llvm"
import "git.tebibyte.media/fspl/fspl/entity" import "git.tebibyte.media/fspl/fspl/entity"
// import "git.tebibyte.media/fspl/fspl/analyzer" import "git.tebibyte.media/fspl/fspl/analyzer"
func (this *generator) generateBlock (block *entity.Block, mode resultMode) (llvm.Value, bool, error) { func (this *generator) generateBlock (block *entity.Block, mode resultMode) (llvm.Value, bool, error) {
if len(block.Steps) == 0 { return nil, false, nil } if len(block.Steps) == 0 { return nil, false, nil }
@ -19,14 +19,28 @@ func (this *generator) generateBlock (block *entity.Block, mode resultMode) (llv
func (this *generator) generateBreak (brk *entity.Break) (llvm.Value, error) { func (this *generator) generateBreak (brk *entity.Break) (llvm.Value, error) {
loopEntry := this.blockManager.topLoop() loopEntry := this.blockManager.topLoop()
var irValue llvm.Value
if brk.Value != nil { if brk.Value != nil {
value, loc, err := this.generateExpression(brk.Value, loopEntry.mode) var err error
var loc bool
thisMode := loopEntry.mode
if thisMode == resultModeAny && len(loopEntry.breakBlocks) > 0 {
// there have been previous break statements, so be
// consistent with the already decided result mode
loc := loopEntry.loc
if loc { thisMode = resultModeLoc
} else { thisMode = resultModeVal }
}
irValue, loc, err = this.generateExpression(brk.Value, loopEntry.mode)
if err != nil { return nil, err } if err != nil { return nil, err }
loopEntry.value = value
loopEntry.loc = loc loopEntry.loc = loc
} }
loopEntry.stub = this.blockManager.Block loopEntry.breakBlocks = append(loopEntry.breakBlocks, &llvm.Incoming {
X: irValue,
Predecessor: this.blockManager.Block,
})
return nil, nil return nil, nil
} }
@ -178,23 +192,135 @@ func (this *generator) generateMatch (match *entity.Match, mode resultMode) (llv
} }
func (this *generator) generateLoop (loop *entity.Loop, mode resultMode) (llvm.Value, bool, error) { func (this *generator) generateLoop (loop *entity.Loop, mode resultMode) (llvm.Value, bool, error) {
irResultType, err := this.generateType(loop.Type())
if err != nil { return nil, false, err }
previous := this.blockManager.Block previous := this.blockManager.Block
body := this.blockManager.newBlock() body := this.blockManager.newBlock()
exit := this.blockManager.newBlock()
previous.NewBr(body) previous.NewBr(body)
loopEntry := this.blockManager.pushLoop(mode) loopEntry := this.blockManager.pushLoop(mode)
defer this.blockManager.popLoop() defer this.blockManager.popLoop()
_, _, err := this.generateExpressionAny(loop.Body) this.blockManager.Block = body
_, _, err = this.generateExpressionAny(loop.Body)
if err != nil { return nil, false, err }
final := this.blockManager.Block
// discard/obtain results
this.blockManager.Block = exit
var irValue llvm.Value
if _, void := irResultType.(*llvm.TypeVoid); void {
loopEntry.tieUp(exit)
} else {
irValue = loopEntry.tieUpPhi(irResultType, exit)
}
if !final.Terminated () {
final.NewBr(body)
}
return irValue, loopEntry.loc, nil
}
func (this *generator) generateFor (loop *entity.For, mode resultMode) (llvm.Value, bool, error) {
irIndexType, err := this.generateTypeIndex()
if err != nil { return nil, false, err }
irResultType, err := this.generateType(loop.Type())
if err != nil { return nil, false, err } if err != nil { return nil, false, err }
this.blockManager.NewBr(body) // element index
exit := this.blockManager.newBlock() var irIndexLoc llvm.Value
if loop.Index != nil {
irIndexLoc, err = this.blockManager.addDeclaration (
loop.Index, llvm.NewConstZeroInitializer(irIndexType))
if err != nil { return nil, false, err }
} else {
irIndexLoc = this.blockManager.newAllocaFront(irIndexType)
}
if loopEntry.stub != nil { // element value
loopEntry.stub.SetTerminator(&llvm.TerminatorBr { irElementLoc, err := this.blockManager.addDeclaration(loop.Element, nil)
Target: exit, if err != nil { return nil, false, err }
irElementType, err := this.generateType(loop.Element.Type())
if err != nil { return nil, false, err }
// source slice/array
irOverLoc, err := this.generateExpressionLoc(loop.Over)
if err != nil { return nil, false, err }
irOverType, err := this.generateType(loop.Over.Type())
if err != nil { return nil, false, err }
baseOverType := analyzer.ReduceToBase(loop.Over.Type())
array, isArray := baseOverType.(*entity.TypeArray)
previous := this.blockManager.Block
header := this.blockManager.newBlock()
body := this.blockManager.newBlock()
exit := this.blockManager.newBlock()
previous.NewBr(header)
loopEntry := this.blockManager.pushLoop(mode)
defer this.blockManager.popLoop()
// check bounds
this.blockManager.Block = header
var irLength llvm.Value
if isArray {
irLength = llvm.NewConstInt(irIndexType, int64(array.Length))
} else {
irLength = this.blockManager.NewLoad (
irIndexType,
this.getSliceLengthFieldLoc(irOverLoc, irOverType))
}
this.blockManager.NewCondBr (
this.blockManager.NewICmp (
llvm.IPredicateULT,
this.blockManager.NewLoad(irIndexType, irIndexLoc),
irLength),
body, exit)
// set element
this.blockManager.Block = body
var irDataLoc llvm.Value
if isArray {
irDataLoc = irOverLoc
} else {
irDataLoc = this.getSliceDataAddress(irOverLoc, irOverType)
}
irElementLocInData := this.blockManager.NewGetElementPtr (
irElementType, irDataLoc,
this.blockManager.NewLoad(irIndexType, irIndexLoc))
this.blockManager.NewStore (
this.blockManager.NewLoad(irElementType, irElementLocInData),
irElementLoc)
// generate loop body
this.blockManager.Block = body
_, _, err = this.generateExpressionAny(loop.Body)
if err != nil { return nil, false, err }
// increment index
this.blockManager.NewStore (
this.blockManager.NewAdd (
this.blockManager.NewLoad(irIndexType, irIndexLoc),
llvm.NewConstInt(irIndexType, 1)),
irIndexLoc)
// discard/obtain results
final := this.blockManager.Block
this.blockManager.Block = exit
var irValue llvm.Value
if _, void := irResultType.(*llvm.TypeVoid); void {
loopEntry.tieUp(exit)
} else {
irValue = loopEntry.tieUpPhi(irResultType, exit, &llvm.Incoming {
X: llvm.NewConstZeroInitializer(irResultType),
Predecessor: header,
}) })
} }
return loopEntry.value, loopEntry.loc, nil
// loop back around
if !final.Terminated () {
final.NewBr(header)
}
return irValue, loopEntry.loc, nil
} }

View File

@ -141,28 +141,28 @@ define void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::Strin
%4 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 1 %4 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 1
%5 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %4 %5 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %4
%6 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %5, 1 %6 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %5, 1
br i1 %6, label %7, label %8 br i1 %6, label %8, label %9
7: 7:
br label %21
8:
%9 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
%10 = load ptr, ptr %9
%11 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 1, ptr %10, i64 1)
%12 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
%13 = load ptr, ptr %12
%14 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 1
%15 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %14
%16 = sub %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %15, 1
%17 = getelementptr i8, ptr %13, i64 1
%18 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 0
store ptr %17, ptr %18
%19 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %16, ptr %19
%20 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %20, ptr %1
br label %3
21:
ret void ret void
8:
br label %7
9:
%10 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
%11 = load ptr, ptr %10
%12 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 1, ptr %11, i64 1)
%13 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
%14 = load ptr, ptr %13
%15 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 1
%16 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %15
%17 = sub %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %16, 1
%18 = getelementptr i8, ptr %14, i64 1
%19 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 0
store ptr %18, ptr %19
%20 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 1
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %17, ptr %20
%21 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %21, ptr %1
br label %3
} }
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 %file, ptr %buffer, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %count) declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 %file, ptr %buffer, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %count)
`, `,

View File

@ -15,7 +15,6 @@ define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
store i64 9186060094042213285, ptr %3 store i64 9186060094042213285, ptr %3
%5 = load double, ptr %1 %5 = load double, ptr %1
store double %5, ptr %4 store double %5, ptr %4
%6 = load %"0zNZN147MN2wzMAQ6NS2dQ==::U", ptr %2
ret void ret void
} }
`, `,

View File

@ -118,6 +118,7 @@ func (this *treeParser) parseExpressionRootIdent () (entity.Expression, error) {
case "if": return this.parseIfElse() case "if": return this.parseIfElse()
case "match": return this.parseMatch() case "match": return this.parseMatch()
case "loop": return this.parseLoop() case "loop": return this.parseLoop()
case "for": return this.parseFor()
default: default:
this.Next() this.Next()
switch this.Kind() { switch this.Kind() {
@ -615,3 +616,38 @@ func (this *treeParser) parseLoop () (*entity.Loop, error) {
Body: body, Body: body,
}, nil }, nil
} }
func (this *treeParser) parseFor () (*entity.For, error) {
err := this.ExpectValue(lexer.Ident, "for")
if err != nil { return nil, err }
this.Next()
loop := &entity.For {
Pos: this.Pos(),
}
firstDeclaration, err := this.parseDeclaration()
if err != nil { return nil, err }
if this.ValueIs("in") {
loop.Element = firstDeclaration
} else {
loop.Index = firstDeclaration
element, err := this.parseDeclaration()
if err != nil { return nil, err }
loop.Element = element
}
err = this.ExpectValue(lexer.Ident, "in")
if err != nil { return nil, err }
this.Next()
over, err := this.parseExpression()
if err != nil { return nil, err }
loop.Over = over
body, err := this.parseExpression()
if err != nil { return nil, err }
loop.Body = body
return loop, nil
}

View File

@ -72,6 +72,8 @@ testString (test,
- [ifElse]:Int = if true then 4 else 5 - [ifElse]:Int = if true then 4 else 5
- [dangleElse]:Int = if true then if false then 3 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]}}
- [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]`,
// input // input
` `
@ -118,6 +120,10 @@ testString (test,
i = [++ i] 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 [matchToInt u:(| Int F64)]:Int = match u
| u:Int u | u:Int u
| u:F64 [~Int u] | u:F64 [~Int u]