Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement FOR/ROF loop macros #60

Merged
merged 3 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading