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)
case *entity.Loop:
return this.analyzeLoop(into, mode, expression)
case *entity.For:
return this.analyzeFor(into, mode, expression)
case *entity.Break:
return this.analyzeBreak(into, mode, expression)
case *entity.Return:

View File

@ -808,6 +808,52 @@ func (this *Tree) analyzeLoop (
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 (
into entity.Type,
mode strictness,

View File

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

View File

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

View File

@ -91,3 +91,54 @@ testUnit (test,
"", "false\n",
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
statements only apply to the closest containing loop. The value of the loop's
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 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
@ -362,6 +368,7 @@ this without hand-writing a parser.
["else" <expression>]
<match> -> "match" <expression> <case>*
<loop> -> "loop" <expression>
<for> -> "for" <expression> [<expression>] in <expression> <expression>
<break> -> "[" "break" [<expression>] "]"
<return> -> "[" "return" [<expression>] "]"
<assignment> -> <expression> "=" <expression>

View File

@ -439,7 +439,14 @@ func (this *Match) String () string {
return out
}
// Breakable is any expression that can be halted using a break.
type Breakable interface {
Expression
breakable()
}
var _ Expression = &Loop { }
var _ Breakable = &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
// 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
}
func (*Loop) expression(){}
func (*Loop) breakable(){}
func (this *Loop) Position () errors.Position { return this.Pos }
func (this *Loop) Type () Type { return this.Ty }
func (this *Loop) HasExplicitType () bool {
@ -467,6 +475,44 @@ func (this *Loop) String () string {
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 { }
// Break allows breaking out of loops. It has no value and may not be assigned
// to anything. It is never a valid location expression.
@ -476,7 +522,7 @@ type Break struct {
Value Expression
// Semantics
Loop *Loop
Loop Breakable
}
func (*Break) expression(){}
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)
if err != nil { return nil, err }
this.blockManager.NewStore(source, destDataFieldLoc)
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
if destinationSpecified {
return nil, nil
} else {
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
}
// conversion from any type to pointer
case *entity.TypePointer:
@ -192,6 +197,8 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) {
// conversion from array to slice
case *entity.TypeArray:
irIndexType, err := this.generateTypeIndex()
if err != nil { return nil, err }
array, err := this.generateExpressionLoc(source)
if err != nil { return nil, err }
irDestType, err := this.generateType(destType)
@ -204,10 +211,12 @@ func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffi
destLengthField := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
this.blockManager.NewStore(array, destDataField)
this.blockManager.NewStore (
llvm.NewConstInt(llvm.I32, int64(sourceTypeBase.Length)),
llvm.NewConstInt(irIndexType, int64(sourceTypeBase.Length)),
destLengthField)
if !destinationSpecified {
if destinationSpecified {
return nil, nil
} else {
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"
type loopEntry struct {
value llvm.Value
stub *llvm.Block
mode resultMode
loc bool
breakBlocks []*llvm.Incoming // incoming value/block pairs of breaks
mode resultMode // result mode for break statements
loc bool // for when mode is resultModeAny
}
// 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 {

View File

@ -76,7 +76,7 @@ testString (test,
`)
}
func TestLoop (test *testing.T) {
func TestLoopSimple (test *testing.T) {
testString (test,
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
0:
@ -105,7 +105,8 @@ testString (test,
2:
br label %3
3:
store i64 5, ptr %1
%4 = phi i64 [ 5, %2 ]
store i64 %4, ptr %1
ret void
}
`,
@ -128,19 +129,20 @@ testString (test,
2:
%3 = load i64, ptr %1
%4 = icmp slt i64 %3, 3
br i1 %4, label %5, label %8
br i1 %4, label %7, label %10
5:
%6 = load i64, ptr %1
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:
%6 = phi i64 [ %8, %7 ]
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:
%4 = load i64, ptr %1
%5 = icmp eq i64 %4, 5
br i1 %5, label %6, label %9
br i1 %5, label %8, label %11
6:
br label %11
7:
%8 = phi i64 [ %10, %9 ]
store i64 %8, ptr %2
br label %3
%7 = phi i64 [ 0, %8 ]
ret i64 %7
8:
br label %6
9:
%10 = load i64, ptr %1
br label %7
%10 = phi i64 [ %12, %11 ]
store i64 %10, ptr %2
br label %3
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)
case *entity.Loop:
return this.generateLoop(expression, resultModeAny)
case *entity.For:
return this.generateFor(expression, resultModeAny)
// we get nothing from these
case *entity.Assignment:
@ -161,6 +163,9 @@ func (this *generator) generateExpressionVal (expression entity.Expression) (llv
case *entity.Loop:
val, _, err := this.generateLoop(expression, resultModeVal)
return val, err
case *entity.For:
val, _, err := this.generateFor(expression, resultModeVal)
return val, err
case *entity.LiteralInt:
return this.generateLiteralInt(expression)
case *entity.LiteralFloat:
@ -233,6 +238,9 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
case *entity.Loop:
loc, _, err := this.generateLoop(expression, resultModeLoc)
return loc, err
case *entity.For:
loc, _, err := this.generateFor(expression, resultModeLoc)
return loc, err
case *entity.LiteralArray:
return this.generateLiteralArrayLoc(expression, nil)
case *entity.LiteralString:

View File

@ -3,7 +3,7 @@ package generator
// import "fmt"
import "git.tebibyte.media/fspl/fspl/llvm"
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) {
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) {
loopEntry := this.blockManager.topLoop()
var irValue llvm.Value
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 }
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
}
@ -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) {
irResultType, err := this.generateType(loop.Type())
if err != nil { return nil, false, err }
previous := this.blockManager.Block
body := this.blockManager.newBlock()
exit := this.blockManager.newBlock()
previous.NewBr(body)
loopEntry := this.blockManager.pushLoop(mode)
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 }
this.blockManager.NewBr(body)
exit := this.blockManager.newBlock()
// element index
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 {
loopEntry.stub.SetTerminator(&llvm.TerminatorBr {
Target: exit,
// element value
irElementLoc, err := this.blockManager.addDeclaration(loop.Element, nil)
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
%5 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %4
%6 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %5, 1
br i1 %6, label %7, label %8
br i1 %6, label %8, label %9
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
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)
`,

View File

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

View File

@ -118,6 +118,7 @@ func (this *treeParser) parseExpressionRootIdent () (entity.Expression, error) {
case "if": return this.parseIfElse()
case "match": return this.parseMatch()
case "loop": return this.parseLoop()
case "for": return this.parseFor()
default:
this.Next()
switch this.Kind() {
@ -615,3 +616,38 @@ func (this *treeParser) parseLoop () (*entity.Loop, error) {
Body: body,
}, 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
- [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]}}
- [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]`,
// input
`
@ -118,6 +120,10 @@ testString (test,
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]