Skip to content

Commit

Permalink
Implement assert (#86)
Browse files Browse the repository at this point in the history
* add logic and comparison operators

* merge overlapping tokOpExpr and tokAddressMode to tokSymbol

* implement assert testing
  • Loading branch information
bobertlo authored Nov 22, 2024
1 parent 1d4e301 commit 949368f
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 50 deletions.
47 changes: 44 additions & 3 deletions compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (c *compiler) expandExpression(expr []token, line int) ([]token, error) {
if labelOk {
val := (label - line) % int(c.m)
if val < 0 {
output = append(output, token{tokExprOp, "-"}, token{tokNumber, fmt.Sprintf("%d", -val)})
output = append(output, token{tokSymbol, "-"}, token{tokNumber, fmt.Sprintf("%d", -val)})
} else {
output = append(output, token{tokNumber, fmt.Sprintf("%d", val)})
}
Expand All @@ -140,6 +140,43 @@ func (c *compiler) expandExpression(expr []token, line int) ([]token, error) {
return output, nil
}

func (c *compiler) evaluateAssertion(assertText string) error {

assertTokens, err := LexInput(strings.NewReader(assertText))
if err != nil {
return err
}
assertTokens = assertTokens[:len(assertTokens)-1]
exprTokens, err := c.expandExpression(assertTokens, 0)
if err != nil {
return err
}
exprVal, err := evaluateExpression(exprTokens)
if err != nil {
return err
}
if exprVal == 0 {
return fmt.Errorf("assertion '%s' failed", assertText)
}
return nil
}

func (c *compiler) evaluateAssertions() error {
for _, line := range c.lines {
if line.typ != lineComment {
continue
}
if strings.HasPrefix(line.comment, ";assert") {
assertText := line.comment[7:]
err := c.evaluateAssertion(assertText)
if err != nil {
return err
}
}
}
return nil
}

func (c *compiler) assembleLine(in sourceLine) (Instruction, error) {
opLower := strings.ToLower(in.op)
var aMode, bMode AddressMode
Expand Down Expand Up @@ -289,7 +326,7 @@ func (c *compiler) expandFor(start, end int) error {
if j == 1 {
newValue = []token{{tokNumber, "0"}}
} else {
newValue = []token{{tokExprOp, "-"}, {tokNumber, fmt.Sprintf("%d", -(1 - j))}}
newValue = []token{{tokSymbol, "-"}, {tokNumber, fmt.Sprintf("%d", -(1 - j))}}
}
}
thisLine = thisLine.subSymbol(label, newValue)
Expand Down Expand Up @@ -351,9 +388,13 @@ func (c *compiler) expandForLoops() error {
}

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

c.loadSymbols()

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

graph := buildReferenceGraph(c.values)
cyclic, cyclicKey := graphContainsCycle(graph)
if cyclic {
Expand Down
28 changes: 28 additions & 0 deletions compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,31 @@ func TestCompileDoubleForLoop(t *testing.T) {
}, w.Code)
assert.Equal(t, 7, w.Start)
}

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

input := `
;assert CORESIZE == 8000
dat.f $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},
}, w.Code)
}

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

input := `
;assert CORESIZE == 8192
dat.f $123, $123
`

w, err := CompileWarrior(strings.NewReader(input), config)
require.Error(t, err)
require.Equal(t, WarriorData{}, w)
}
14 changes: 10 additions & 4 deletions expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ func expandExpressions(values map[string][]token, graph map[string][]string) (ma

func combineSigns(expr []token) []token {
out := make([]token, 0, len(expr))
lastOut := token{tokEOF, ""}
lastOut := token{typ: tokEOF}

// please forgive me for this lol
for i := 0; i < len(expr); i++ {
if lastOut.typ == tokExprOp {
if lastOut.typ == tokSymbol {
negativeFound := false
for ; i < len(expr); i++ {
if !(expr[i].val == "-" || expr[i].val == "+") {
Expand All @@ -90,7 +90,7 @@ func combineSigns(expr []token) []token {
}
}
if negativeFound {
out = append(out, token{tokExprOp, "-"})
out = append(out, token{tokSymbol, "-"})
}
if i < len(expr) {
out = append(out, expr[i])
Expand All @@ -111,7 +111,7 @@ func flipDoubleNegatives(expr []token) []token {
for i := 0; i < len(expr); i++ {
if expr[i].val == "-" {
if i+1 < len(expr) && expr[i+1].val == "-" {
out = append(out, token{tokExprOp, "+"})
out = append(out, token{tokSymbol, "+"})
i += 1
continue
}
Expand All @@ -122,6 +122,12 @@ func flipDoubleNegatives(expr []token) []token {
}

func evaluateExpression(expr []token) (int, error) {
for _, tok := range expr {
if tok.typ == tokText || !tok.IsExpressionTerm() {
return 0, fmt.Errorf("unexpected token in expressoin: '%s'", tok)
}
}

combinedExpr := combineSigns(expr)
flippedExpr := flipDoubleNegatives(combinedExpr)

Expand Down
39 changes: 30 additions & 9 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
func TestExpandExpressions(t *testing.T) {
values := map[string][]token{
"a": {{tokNumber, "1"}},
"c": {{tokText, "a"}, {tokExprOp, "*"}, {tokText, "b"}},
"b": {{tokText, "a"}, {tokExprOp, "+"}, {tokNumber, "2"}},
"c": {{tokText, "a"}, {tokSymbol, "*"}, {tokText, "b"}},
"b": {{tokText, "a"}, {tokSymbol, "+"}, {tokNumber, "2"}},
}
graph := map[string][]string{
"b": {"a"},
Expand All @@ -23,8 +23,8 @@ func TestExpandExpressions(t *testing.T) {
require.NoError(t, err)
require.Equal(t, map[string][]token{
"a": {{tokNumber, "1"}},
"b": {{tokNumber, "1"}, {tokExprOp, "+"}, {tokNumber, "2"}},
"c": {{tokNumber, "1"}, {tokExprOp, "*"}, {tokNumber, "1"}, {tokExprOp, "+"}, {tokNumber, "2"}},
"b": {{tokNumber, "1"}, {tokSymbol, "+"}, {tokNumber, "2"}},
"c": {{tokNumber, "1"}, {tokSymbol, "*"}, {tokNumber, "1"}, {tokSymbol, "+"}, {tokNumber, "2"}},
}, output)
}

Expand All @@ -37,17 +37,17 @@ func TestCombineSigns(t *testing.T) {
input: "1++-2",
output: []token{
{tokNumber, "1"},
{tokExprOp, "+"},
{tokExprOp, "-"},
{tokSymbol, "+"},
{tokSymbol, "-"},
{tokNumber, "2"},
},
},
{
input: "1-+-2",
output: []token{
{tokNumber, "1"},
{tokExprOp, "-"},
{tokExprOp, "-"},
{tokSymbol, "-"},
{tokSymbol, "-"},
{tokNumber, "2"},
},
},
Expand All @@ -73,7 +73,7 @@ func TestFlipDoubleNegatives(t *testing.T) {
input: "1--1",
output: []token{
{tokNumber, "1"},
{tokExprOp, "+"},
{tokSymbol, "+"},
{tokNumber, "1"},
},
},
Expand Down Expand Up @@ -103,13 +103,33 @@ func TestEvaluateExpressionPositive(t *testing.T) {

// handle signs
"1 - -1": 2,

// logic
"1 > 2": 0,
"2 > 1": 1,
"1 < 2": 1,
"2 < 1": 0,
"1 >= 1": 1,
"2 <= 2": 1,
"8000 == 8000": 1,
"8000 == 800": 0,
// hmmm, these need to be fixed
// "1 && 1": 1,
// "1 && 0": 0,
// "1 || 1": 1,
// "1 || 0": 0,
"2 == 1 || 2 == 2": 1,
"2 == 1 || 2 == 3": 0,
}

for input, expected := range testCases {
lexer := newLexer(strings.NewReader(input))
tokens, err := lexer.Tokens()
require.NoError(t, err)

// trim EOF from input
tokens = tokens[:len(tokens)-1]

val, err := evaluateExpression(tokens)
require.NoError(t, err)
assert.Equal(t, expected, val)
Expand All @@ -120,6 +140,7 @@ func TestEvaluateExpressionNegative(t *testing.T) {
cases := []string{
")21",
"2^3",
"2{2",
}

for _, input := range cases {
Expand Down
72 changes: 60 additions & 12 deletions lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ func newLexer(r io.Reader) *lexer {
return lex
}

func LexInput(r io.Reader) ([]token, error) {
lexer := newLexer(r)
return lexer.Tokens()
}

func (l *lexer) next() (rune, bool) {
if l.atEOF {
return '\x00', true
Expand Down Expand Up @@ -155,15 +160,12 @@ func lexInput(l *lexer) lexStateFn {
return lexNumber
}

// handle comments
if l.nextRune == ';' {
return lexComment
}

// dispatch based on next rune, or error
switch l.nextRune {
case '\x00':
l.tokens <- token{tokEOF, ""}
case ';':
return lexComment
case ',':
return l.emitConsume(token{tokComma, ","}, lexInput)
case '(':
Expand All @@ -179,7 +181,7 @@ func lexInput(l *lexer) lexStateFn {
case '/':
fallthrough
case '%':
return l.emitConsume(token{tokExprOp, string(l.nextRune)}, lexInput)
return l.emitConsume(token{tokSymbol, string(l.nextRune)}, lexInput)
case '$':
fallthrough
case '#':
Expand All @@ -189,13 +191,19 @@ func lexInput(l *lexer) lexStateFn {
case '{':
fallthrough
case '}':
fallthrough
return l.emitConsume(token{tokSymbol, string(l.nextRune)}, lexInput)
case '<':
fallthrough
return l.consume(lexLt)
case '>':
return l.emitConsume(token{tokAddressMode, string(l.nextRune)}, lexInput)
return l.consume(lexGt)
case ':':
return l.emitConsume(token{tokColon, ":"}, lexInput)
case '=':
return l.consume(lexEquals)
case '|':
return l.consume(lexPipe)
case '&':
return l.consume(lexAnd)
case '\x1a':
return l.consume(lexInput)
default:
Expand Down Expand Up @@ -276,7 +284,47 @@ func lexComment(l *lexer) lexStateFn {
return lexInput
}

func LexInput(r io.Reader) ([]token, error) {
lexer := newLexer(r)
return lexer.Tokens()
func lexEquals(l *lexer) lexStateFn {
if l.nextRune == '=' {
return l.emitConsume(token{tokSymbol, "=="}, lexInput)
} else {
l.tokens <- token{tokError, fmt.Sprintf("expected '=' after '=', got '%s'", string(l.nextRune))}
return nil
}
}

func lexPipe(l *lexer) lexStateFn {
if l.nextRune == '|' {
return l.emitConsume(token{tokSymbol, "||"}, lexInput)
} else {
l.tokens <- token{tokError, fmt.Sprintf("expected '|' after '|', got '%s'", string(l.nextRune))}
return nil
}
}

func lexAnd(l *lexer) lexStateFn {
if l.nextRune == '&' {
return l.emitConsume(token{tokSymbol, "&&"}, lexInput)
} else {
l.tokens <- token{tokError, fmt.Sprintf("expected '&' after '&', got '%s'", string(l.nextRune))}
return nil
}
}

func lexGt(l *lexer) lexStateFn {
if l.nextRune == '=' {
return l.emitConsume(token{tokSymbol, ">="}, lexInput)
} else {
l.tokens <- token{tokSymbol, ">"}
return lexInput
}
}

func lexLt(l *lexer) lexStateFn {
if l.nextRune == '=' {
return l.emitConsume(token{tokSymbol, "<="}, lexInput)
} else {
l.tokens <- token{tokSymbol, "<"}
return lexInput
}
}
Loading

0 comments on commit 949368f

Please sign in to comment.