Skip to content

Commit

Permalink
Implement FOR/ROF loop macros (#60)
Browse files Browse the repository at this point in the history
* add for loops, basic tests

* add parens to IsExpressionTerm, add more test warriors

* add test for double for loop
  • Loading branch information
bobertlo authored Nov 17, 2024
1 parent 814077e commit 64c2e90
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vmars
gmars
121 changes: 119 additions & 2 deletions compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type compiler struct {
func newCompiler(src []sourceLine, metadata WarriorData, config SimulatorConfig) (*compiler, error) {
err := config.Validate()
if err != nil {
return nil, fmt.Errorf("invalid condif: %s", err)
return nil, fmt.Errorf("invalid config: %s", err)
}
return &compiler{
lines: src,
Expand Down Expand Up @@ -59,6 +59,24 @@ func (c *compiler) loadSymbols() {
}
}

func (c *compiler) reloadReferences() error {
c.labels = make(map[string]int)

for _, line := range c.lines {
if line.typ == lineInstruction {
for _, label := range line.labels {
_, ok := c.labels[label]
if ok {
return fmt.Errorf("line %d: label '%s' redefined", line.line, label)
}
c.labels[label] = line.codeLine
}
}
}

return nil
}

func (c *compiler) expandExpression(expr []token, line int) ([]token, error) {
input := expr
var output []token
Expand Down Expand Up @@ -196,13 +214,107 @@ func (c *compiler) assembleLine(in sourceLine) (Instruction, error) {
}, nil
}

func (c *compiler) expandFor(start, end int) error {
output := make([]sourceLine, 0)
codeLineIndex := 0

// concatenate lines preceding start
for i := 0; i < start; i++ {
// curLine := c.lines[i]
if c.lines[i].typ == lineInstruction {
// curLine.line = codeLineIndex
codeLineIndex++
}
output = append(output, c.lines[i])
}

// get labels and count from for line
labels := c.lines[start].labels
count, err := evaluateExpression(c.lines[start].a)
if err != nil {
return fmt.Errorf("line %d: invalid for count '%s", c.lines[start].line, c.lines[start].a)
}

for j := 1; j <= count; j++ {
for i := start + 1; i < end; i++ {
if c.lines[i].typ == lineInstruction {
thisLine := c.lines[i]

// subtitute symbols in line
for iLabel, label := range labels {
var newValue []token
if iLabel == len(labels)-1 {
newValue = []token{{tokNumber, fmt.Sprintf("%d", j)}}
} else {
if j == 1 {
newValue = []token{{tokNumber, "0"}}
} else {
newValue = []token{{tokExprOp, "-"}, {tokNumber, fmt.Sprintf("%d", -(1 - j))}}
}
}
thisLine = thisLine.subSymbol(label, newValue)
}

// update codeLine
thisLine.codeLine = codeLineIndex
codeLineIndex++

output = append(output, thisLine)
} else {
output = append(output, c.lines[i])
}
}

}

// continue appending lines until the end of the file
for i := end + 1; i < len(c.lines); i++ {
if c.lines[i].typ == lineInstruction {
thisLine := c.lines[i]
thisLine.codeLine = codeLineIndex
codeLineIndex++
output = append(output, thisLine)
} else {
output = append(output, c.lines[i])
}
}

c.lines = output
return c.reloadReferences()
}

// look for for statements from the bottom up. if one is found it is expanded
// and the function calls itself again.
func (c *compiler) expandForLoops() error {
rofSourceIndex := -1
for i := len(c.lines) - 1; i >= 0; i-- {
if c.lines[i].typ == linePseudoOp {
lop := strings.ToLower(c.lines[i].op)
if lop == "rof" {
rofSourceIndex = i
} else if lop == "for" {
if rofSourceIndex == -1 {
return fmt.Errorf("line %d: unmatched for", c.lines[i].codeLine)
}
c.expandFor(i, rofSourceIndex)
return c.expandForLoops()
}
}
}
if rofSourceIndex != -1 {
return fmt.Errorf("line %d: unmatched rof", c.lines[rofSourceIndex].line)
}
return nil
}

func (c *compiler) compile() (WarriorData, error) {

c.loadSymbols()

graph := buildReferenceGraph(c.values)
cyclic, cyclicKey := graphContainsCycle(graph)
if cyclic {
return WarriorData{}, fmt.Errorf("expressiong '%s' is cyclic", cyclicKey)
return WarriorData{}, fmt.Errorf("expression '%s' is cyclic", cyclicKey)
}

resolved, err := expandExpressions(c.values, graph)
Expand All @@ -211,6 +323,11 @@ func (c *compiler) compile() (WarriorData, error) {
}
c.values = resolved

err = c.expandForLoops()
if err != nil {
return WarriorData{}, err
}

code := make([]Instruction, 0)
for _, line := range c.lines {
if line.typ != lineInstruction {
Expand Down
62 changes: 60 additions & 2 deletions compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gmars
import (
"fmt"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -48,8 +49,6 @@ func runWarriorLoadFileTests(t *testing.T, tests []warriorTestCase) {
defer loadInput.Close()
expectedData, err := ParseLoadFile(loadInput, test.config)
require.NoError(t, err)

// assert.NoError(t, err)
assert.Equal(t, expectedData.Code, warriorData.Code)
}
}
Expand Down Expand Up @@ -109,7 +108,66 @@ func TestCompileWarriorsFile94(t *testing.T) {
loadFilename: "test_files/scaryvampire.rc",
config: config,
},
{
filename: "warriors/94/bombspiral.red",
loadFilename: "test_files/bombspiral.rc",
config: config,
},
{
filename: "warriors/94/paperhaze.red",
loadFilename: "test_files/paperhaze.rc",
config: config,
},
}

runWarriorLoadFileTests(t, tests)
}

func TestCompileForLoop(t *testing.T) {
config := ConfigNOP94()

input := `
dat 123, 123
i j for 3
dat i, j
rof
dat 123, 123
`

w, err := CompileWarrior(strings.NewReader(input), config)
require.NoError(t, err)
assert.Equal(t, []Instruction{
{Op: DAT, OpMode: F, AMode: DIRECT, A: 123, BMode: DIRECT, B: 123},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 0, BMode: DIRECT, B: 1},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 7999, BMode: DIRECT, B: 2},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 7998, BMode: DIRECT, B: 3},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 123, BMode: DIRECT, B: 123},
}, w.Code)
}

func TestCompileDoubleForLoop(t *testing.T) {
config := ConfigNOP94()

input := `
dat 123, 123
i for 3
j for 2
dat i, j
rof
rof
dat 123, 123
`

w, err := CompileWarrior(strings.NewReader(input), config)
require.NoError(t, err)
assert.Equal(t, []Instruction{
{Op: DAT, OpMode: F, AMode: DIRECT, A: 123, BMode: DIRECT, B: 123},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 1, BMode: DIRECT, B: 1},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 1, BMode: DIRECT, B: 2},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 2, BMode: DIRECT, B: 1},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 2, BMode: DIRECT, B: 2},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 3, BMode: DIRECT, B: 1},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 3, BMode: DIRECT, B: 2},
{Op: DAT, OpMode: F, AMode: DIRECT, A: 123, BMode: DIRECT, B: 123},
}, w.Code)
}
25 changes: 24 additions & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ type sourceLine struct {
newlines int
}

func subExprSymbol(expr []token, label string, value []token) []token {
output := make([]token, 0, len(expr))
for _, tok := range expr {
if tok.typ == tokText && tok.val == label {
output = append(output, value...)
} else {
output = append(output, tok)
}
}
return output
}

func (line sourceLine) subSymbol(label string, value []token) sourceLine {
// output := make()
line.a = subExprSymbol(line.a, label, value)
line.b = subExprSymbol(line.b, label, value)
return line
}

type parser struct {
lex *lexer

Expand Down Expand Up @@ -244,6 +263,7 @@ func parsePseudoOp(p *parser) parseStateFn {
return parseComment
} else if p.nextToken.typ == tokNewline {
if lastToken.NoOperandsOk() {
p.next()
p.currentLine.newlines += 1
p.lines = append(p.lines, p.currentLine)
return parseLine
Expand Down Expand Up @@ -279,7 +299,10 @@ func parsePseudoExpr(p *parser) parseStateFn {
case tokComment:
return parseComment
case tokNewline:
fallthrough
p.next()
p.currentLine.newlines += 1
p.lines = append(p.lines, p.currentLine)
return parseLine
case tokEOF:
p.lines = append(p.lines, p.currentLine)
return parseLine
Expand Down
98 changes: 98 additions & 0 deletions test_files/bombspiral.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
ORG 0
SPL.B $ 91, $ 0
JMP.B $ 8, $ 0
SPL.B # 0, $ 0
SPL.B $ 0, $ 0
MOV.I $ 3, $ -953
ADD.AB # -953, $ -1
DJN.F $ -2, < -2445
DAT.F > -1, { 1
DAT.F # 0, # -1333
MOV.I { -1, < -1
MOV.I { -2, < -2
MOV.I { -3, < -3
MOV.I { -4, < -4
MOV.I { -5, < -5
MOV.I { -6, < -6
JMP.B @ -7, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
DAT.F $ 0, $ 0
SPL.B # 0, > 1
MOV.I $ 3, $ 3
ADD.A # 1144, $ 1
JMP.B $ -1143, $ 0
MOV.I # 0, $ 1143
END
Loading

0 comments on commit 64c2e90

Please sign in to comment.