diff --git a/compile.go b/compile.go index 2583b09..5359b7d 100644 --- a/compile.go +++ b/compile.go @@ -75,34 +75,6 @@ func (c *compiler) loadSymbols() { } } -func (c *compiler) reloadReferences() error { - c.labels = make(map[string]int) - - var curPseudoLine 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 - curPseudoLine = line.codeLine + 1 - } - } else if line.typ == linePseudoOp { - 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] = curPseudoLine - } - } - } - - return nil -} - func (c *compiler) expandExpression(expr []token, line int) ([]token, error) { input := expr var output []token @@ -286,107 +258,6 @@ 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 - - countExpr, err := c.expandExpression(c.lines[start].a, start) - if err != nil { - return err - } - count, err := evaluateExpression(countExpr) - 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{{tokSymbol, "-"}, {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) - } - err := c.expandFor(i, rofSourceIndex) - if err != nil { - return err - } - 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() @@ -407,11 +278,6 @@ 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 { @@ -451,11 +317,28 @@ func CompileWarrior(r io.Reader, config SimulatorConfig) (WarriorData, error) { return WarriorData{}, err } - // scanner := newSymbolScanner(newBufTokenReader(tokens)) - // _, err = scanner.ScanInput() - // if err != nil { - // return WarriorData{}, fmt.Errorf("symbol scanner: %s", err) - // } + depth := 0 + for { + symbols, forSeen, err := ScanInput(newBufTokenReader(tokens)) + if err != nil { + return WarriorData{}, fmt.Errorf("symbol scanner: %s", err) + } + if forSeen { + expandedTokens, err := ForExpand(newBufTokenReader(tokens), symbols) + if err != nil { + return WarriorData{}, fmt.Errorf("for: %s", err) + } + tokens = expandedTokens + // oops the embedded for loops are not implemented + // break + } else { + break + } + depth++ + if depth > 12 { + return WarriorData{}, fmt.Errorf("for loop depth exceeded") + } + } parser := newParser(newBufTokenReader(tokens)) sourceLines, metadata, err := parser.parse() diff --git a/compile_test.go b/compile_test.go index 1d6628d..2b2c9c1 100644 --- a/compile_test.go +++ b/compile_test.go @@ -43,12 +43,12 @@ func runWarriorLoadFileTests(t *testing.T, tests []warriorTestCase) { if test.err { assert.Error(t, err, fmt.Sprintf("%s: error should be present", test.filename)) } else { - require.NoError(t, err) + require.NoError(t, err, test.loadFilename) loadInput, err := os.Open(test.loadFilename) - require.NoError(t, err) + require.NoError(t, err, test.loadFilename) defer loadInput.Close() expectedData, err := ParseLoadFile(loadInput, test.config) - require.NoError(t, err) + require.NoError(t, err, test.loadFilename) assert.Equal(t, expectedData.Code, warriorData.Code) } } diff --git a/expr.go b/expr.go index b7281ee..968d8f8 100644 --- a/expr.go +++ b/expr.go @@ -7,6 +7,34 @@ import ( "strconv" ) +func ExpandAndEvaluate(expr []token, symbols map[string][]token) (int, error) { + graph := buildReferenceGraph(symbols) + + cyclic, key := graphContainsCycle(graph) + if cyclic { + return 0, fmt.Errorf("symbol graph contains cycles: %s", key) + } + + resolved, err := expandExpressions(symbols, graph) + if err != nil { + return 0, err + } + + expanded := make([]token, 0) + for _, tok := range expr { + if tok.typ == tokText { + symVal, ok := resolved[tok.val] + if ok { + expanded = append(expanded, symVal...) + continue + } + } + expanded = append(expanded, tok) + } + + return evaluateExpression(expanded) +} + func expandValue(key string, values, resolved map[string][]token, graph map[string][]string) ([]token, error) { // load key value or error value, valOk := values[key] diff --git a/forexpand.go b/forexpand.go new file mode 100644 index 0000000..217b79b --- /dev/null +++ b/forexpand.go @@ -0,0 +1,374 @@ +package gmars + +import ( + "fmt" + "strings" +) + +type forExpander struct { + lex tokenReader + + // lexing state fields + nextToken token + labelBuf []string + exprBuf []token + atEOF bool + + // for state fields + forCountLabel string + forLineLabels []string + forLineLabelsToWrite []string + forCount int + forIndex int + forContent []token + forDepth int + + symbols map[string][]token + + // output fields + tokens chan token + closed bool +} + +type forStateFn func(f *forExpander) forStateFn + +func newForExpander(lex tokenReader, symbols map[string][]token) *forExpander { + f := &forExpander{lex: lex, symbols: symbols} + f.next() + f.tokens = make(chan token) + go f.run() + return f +} + +func ForExpand(lex tokenReader, symbols map[string][]token) ([]token, error) { + expander := newForExpander(lex, symbols) + tokens, err := expander.Tokens() + if err != nil { + return nil, err + } + return tokens, nil +} + +func (p *forExpander) next() token { + if p.atEOF { + return token{typ: tokEOF} + } + tok, err := p.lex.NextToken() + if err != nil { + p.atEOF = true + return token{tokError, fmt.Sprintf("%s\n", err)} + } + if tok.typ == tokEOF || tok.typ == tokError { + p.atEOF = true + } + retTok := p.nextToken + p.nextToken = tok + return retTok +} + +func (f *forExpander) run() { + if f.closed || f.atEOF { + return + } + for state := forLine; state != nil; { + state = state(f) + } + + // add an extra EOF in case we end without one + // we don't want to block on reading from the channel + f.tokens <- token{tokEOF, ""} + f.closed = true +} + +func (f *forExpander) NextToken() (token, error) { + if f.closed { + return token{}, fmt.Errorf("no more tokens") + } + return <-f.tokens, nil +} + +func (f *forExpander) Tokens() ([]token, error) { + if f.closed { + return nil, fmt.Errorf("no more tokens") + } + tokens := make([]token, 0) + for !f.closed { + tok := <-f.tokens + tokens = append(tokens, tok) + if tok.typ == tokEOF || tok.typ == tokError { + break + } + } + return tokens, nil +} + +func (f *forExpander) emitConsume(nextState forStateFn) forStateFn { + f.tokens <- f.nextToken + f.next() + return nextState +} + +// forLine is the base state and returned to after every newline outside a for loop +// text: forConsumeLabels +// anything else: forConsumeLine +func forLine(f *forExpander) forStateFn { + switch f.nextToken.typ { + case tokText: + f.labelBuf = make([]string, 0) + return forConsumeLabels + default: + return forConsumeEmitLine + } +} + +// consume labels into labelBuf and go to next state +// text "for": forFor +// text op/pseudo: forWriteLabelsConsumeLine +// text other: append to labelBuf, forConsumeLabels +// newline/comment: forConsumeLabels +// other: nil +func forConsumeLabels(f *forExpander) forStateFn { + if f.nextToken.typ == tokText { + if f.nextToken.IsPseudoOp() { + opLower := strings.ToLower(f.nextToken.val) + if opLower == "for" { + f.next() + f.exprBuf = make([]token, 0) + return forConsumeExpression + } else { + return forWriteLabelsEmitConsumeLine + } + } else if f.nextToken.IsOp() { + return forWriteLabelsEmitConsumeLine + } else { + f.labelBuf = append(f.labelBuf, f.nextToken.val) + f.next() + return forConsumeLabels + } + } else if f.nextToken.typ == tokNewline || f.nextToken.typ == tokComment || f.nextToken.typ == tokColon { + f.next() + return forConsumeLabels + } else { + f.tokens <- token{tokError, fmt.Sprintf("expected label, op, newlines, or comment, got '%s'", f.nextToken)} + return nil + } +} + +// forWriteLabelsEmitConsumeLine writes all the stored labels to the token channel, +// emits the current nextToken and returns forConsumeLine +func forWriteLabelsEmitConsumeLine(f *forExpander) forStateFn { + for _, label := range f.labelBuf { + f.tokens <- token{tokText, label} + } + f.labelBuf = make([]string, 0) + return f.emitConsume(forConsumeEmitLine) +} + +// forConsumeEmitLine consumes and emits tokens until a newline is reached +// the newline is consumed and emitted before calling forLine +func forConsumeEmitLine(f *forExpander) forStateFn { + switch f.nextToken.typ { + case tokNewline: + return f.emitConsume(forLine) + case tokError: + return f.emitConsume(nil) + case tokEOF: + return f.emitConsume(nil) + default: + return f.emitConsume(forConsumeEmitLine) + } +} + +// forConsumeExpressions consumes tokens into the exprBuf until +// a newline is reached then returns forInnerLine after consuming +// the newline to +// newline: forFor +// error: emit, nil +// eof: nil +// otherwise: forConsumeExpression +func forConsumeExpression(f *forExpander) forStateFn { + switch f.nextToken.typ { + case tokNewline: + f.next() + return forFor + case tokComment: + f.next() + return forConsumeExpression + case tokError: + return f.emitConsume(nil) + case tokEOF: + return nil + default: + // f.tokens <- f.nextToken + f.exprBuf = append(f.exprBuf, f.nextToken) + f.next() + return forConsumeExpression + } +} + +// input: exprBuf from forConsumeExpression +// evaluates count expression and sets up for state +// always returns forInnerLine or Error +func forFor(f *forExpander) forStateFn { + expr := make([]token, 0, len(f.exprBuf)) + for _, tok := range f.exprBuf { + if tok.typ == tokEOF || tok.typ == tokError { + f.tokens <- token{tokError, fmt.Sprintf("unexpected expression term: %s", tok)} + } + expr = append(expr, tok) + } + f.exprBuf = expr + + val, err := ExpandAndEvaluate(f.exprBuf, f.symbols) + if err != nil { + f.tokens <- token{tokError, fmt.Sprintf("%s", err)} + return nil + } + + if len(f.labelBuf) > 0 { + f.forCountLabel = f.labelBuf[len(f.labelBuf)-1] + + if len(f.labelBuf) > 1 { + f.forLineLabels = f.labelBuf[:len(f.labelBuf)-1] + } else { + f.forLineLabels = []string{} + } + } else { + f.forCountLabel = "" + f.forLineLabels = []string{} + } + + f.forLineLabelsToWrite = make([]string, len(f.forLineLabels)) + for i, label := range f.forLineLabels { + f.forLineLabelsToWrite[i] = fmt.Sprintf("__for_%s_%s", f.forCountLabel, label) + } + + f.forCount = val + f.forIndex = 0 // should not be necessary + f.forContent = make([]token, 0) + f.labelBuf = make([]string, 0) + + return forInnerLine +} + +// text: forInnerConsumeLabels +// other: forInnerConsumeLine +func forInnerLine(f *forExpander) forStateFn { + switch f.nextToken.typ { + case tokText: + f.labelBuf = make([]string, 0) + return forInnerLabels + default: + // emitconsume line into for buffer + return forInnerEmitConsumeLine + } +} + +// this is really just to drop labels before 'rof' +func forInnerLabels(f *forExpander) forStateFn { + switch f.nextToken.typ { + case tokText: + if f.nextToken.IsPseudoOp() { + opLower := strings.ToLower(f.nextToken.val) + if opLower == "for" { + f.forDepth += 1 + return forInnerEmitLabels + } else if opLower == "rof" { + if f.forDepth > 0 { + f.forDepth -= 1 + return forInnerEmitConsumeLine + } else { + return forRof + } + } else { + return forInnerEmitLabels + } + } else if f.nextToken.IsOp() { + if f.forLineLabelsToWrite != nil { + for _, label := range f.forLineLabelsToWrite { + f.tokens <- token{tokText, label} + } + f.forLineLabelsToWrite = nil + } + return forInnerEmitLabels + } else { + f.labelBuf = append(f.labelBuf, f.nextToken.val) + f.next() + return forInnerLabels + } + default: + // not expecting legal input here, but we will let the parser deal with it + return forInnerEmitLabels + } +} + +func forInnerEmitLabels(f *forExpander) forStateFn { + for _, label := range f.labelBuf { + f.forContent = append(f.forContent, token{tokText, label}) + } + return forInnerEmitConsumeLine +} + +func forInnerEmitConsumeLine(f *forExpander) forStateFn { + switch f.nextToken.typ { + case tokError: + f.tokens <- f.nextToken + return nil + case tokEOF: + return nil + case tokNewline: + f.forContent = append(f.forContent, f.nextToken) + f.next() + return forInnerLine + default: + f.forContent = append(f.forContent, f.nextToken) + f.next() + return forInnerEmitConsumeLine + } +} + +func forRof(f *forExpander) forStateFn { + for f.nextToken.typ != tokNewline { + if f.nextToken.typ == tokEOF || f.nextToken.typ == tokError { + f.tokens <- f.nextToken + return nil + } + f.next() + } + f.next() + + for i := 1; i <= f.forCount; i++ { + for _, tok := range f.forContent { + if tok.typ == tokText { + if tok.val == f.forCountLabel { + f.tokens <- token{tokNumber, fmt.Sprintf("%d", i)} + } else { + found := false + for _, label := range f.forLineLabels { + forLabel := fmt.Sprintf("__for_%s_%s", f.forCountLabel, label) + if tok.val == label { + f.tokens <- token{tokText, forLabel} + found = true + break + } + } + if !found { + f.tokens <- tok + } + } + } else { + f.tokens <- tok + } + } + } + + return forEmitConsumeStream +} + +func forEmitConsumeStream(f *forExpander) forStateFn { + for f.nextToken.typ != tokEOF { + f.tokens <- f.nextToken + f.next() + } + return nil +} diff --git a/forexpand_test.go b/forexpand_test.go new file mode 100644 index 0000000..540daf5 --- /dev/null +++ b/forexpand_test.go @@ -0,0 +1,131 @@ +package gmars + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +type forTestCase struct { + input string + symbols map[string][]token + output []token +} + +func runForExpanderTests(t *testing.T, cases []forTestCase) { + for _, test := range cases { + tokens, err := LexInput(strings.NewReader(test.input)) + require.NoError(t, err) + require.NotNil(t, tokens) + + // scanner := newSymbolScanner(newBufTokenReader(tokens)) + expander := newForExpander(newBufTokenReader(tokens), test.symbols) + outTokens, err := expander.Tokens() + require.NoError(t, err) + require.Equal(t, test.output, outTokens) + } +} + +func TestForExpander(t *testing.T) { + tests := []forTestCase{ + { + input: "i for 2\nj for 2\ndat i, j\nrof\nrof\n", + output: []token{ + {tokText, "j"}, + {tokText, "for"}, + {tokNumber, "2"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "1"}, + {tokComma, ","}, + {tokText, "j"}, + {tokNewline, ""}, + {tokText, "rof"}, + {tokNewline, ""}, + {tokText, "j"}, + {tokText, "for"}, + {tokNumber, "2"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "2"}, + {tokComma, ","}, + {tokText, "j"}, + {tokNewline, ""}, + {tokText, "rof"}, + {tokNewline, ""}, + {tokEOF, ""}, + }, + }, + { + input: "i for 2\ndat 0, i\nrof\n", + output: []token{ + {tokText, "dat"}, + {tokNumber, "0"}, + {tokComma, ","}, + {tokNumber, "1"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "0"}, + {tokComma, ","}, + {tokNumber, "2"}, + {tokNewline, ""}, + {tokEOF, ""}, + }, + }, + { + input: "i for 2\ndat 0, i\nrof\ndat 3, 4\n", + output: []token{ + {tokText, "dat"}, + {tokNumber, "0"}, + {tokComma, ","}, + {tokNumber, "1"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "0"}, + {tokComma, ","}, + {tokNumber, "2"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "3"}, + {tokComma, ","}, + {tokNumber, "4"}, + {tokNewline, ""}, + {tokEOF, ""}, + }, + }, + // no for + { + input: "test equ 2\ndat 0, test\n", + output: []token{ + {tokText, "test"}, + {tokText, "equ"}, + {tokNumber, "2"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "0"}, + {tokComma, ","}, + {tokText, "test"}, + {tokNewline, ""}, + {tokEOF, ""}, + }, + }, + { + input: "test test2 equ 2\ndat 0, test\n", + output: []token{ + {tokText, "test"}, + {tokText, "test2"}, + {tokText, "equ"}, + {tokNumber, "2"}, + {tokNewline, ""}, + {tokText, "dat"}, + {tokNumber, "0"}, + {tokComma, ","}, + {tokText, "test"}, + {tokNewline, ""}, + {tokEOF, ""}, + }, + }, + } + runForExpanderTests(t, tests) +} diff --git a/load.go b/load.go index 4a8b1bf..a62552f 100644 --- a/load.go +++ b/load.go @@ -223,10 +223,12 @@ func getOpModeAndValidate88(Op OpCode, AMode AddressMode, BMode AddressMode) (Op case SLT: // SLT; - // AB if #A, B otherwise, no #B allowed - if BMode == IMMEDIATE { - return 0, fmt.Errorf("invalid b mode '#' for op 'slt'") - } + // AB if #A, B otherwise + + // #B is not allowed by the 88 standard but is allowed on hills + // if BMode == IMMEDIATE { + // return 0, fmt.Errorf("invalid b mode '#' for op 'slt'") + // } if AMode == IMMEDIATE { return AB, nil } else { diff --git a/load_test.go b/load_test.go index ed84ef6..62d4209 100644 --- a/load_test.go +++ b/load_test.go @@ -66,11 +66,12 @@ func TestLoadDwarf(t *testing.T) { }, data.Code) } -func TestValidInput(t *testing.T) { +func TestValidInput88(t *testing.T) { // random inputs that are valid but not worth validating output cases := []string{ "END\n", "\n\n", + "SLT $ 0, # 0\n", // not in 88 spec, pMARS supports this though } config := ConfigKOTH88 @@ -112,7 +113,7 @@ func TestInvalidInput(t *testing.T) { "DAT # 0, $ 0\n", "DAT # 0, @ 0\n", "CMP $ 0, # 0\n", - "SLT $ 0, # 0\n", + // "SLT $ 0, # 0\n", // pMARS supports this "ADD $ 0, # 0\n", "SUB $ 0, # 0\n", "JMP # 0, $ 0\n", @@ -192,6 +193,12 @@ func TestValidOpModeCombos88(t *testing.T) { {"SLT < 1, @ 2\n", Instruction{Op: SLT, OpMode: B, AMode: B_DECREMENT, A: 1, BMode: B_INDIRECT, B: 2}}, {"SLT < 1, < 2\n", Instruction{Op: SLT, OpMode: B, AMode: B_DECREMENT, A: 1, BMode: B_DECREMENT, B: 2}}, + // these are not listed as legal instructions but do run in pMARS + {"SLT # 1, # 2\n", Instruction{Op: SLT, OpMode: AB, AMode: IMMEDIATE, A: 1, BMode: IMMEDIATE, B: 2}}, + {"SLT $ 1, # 2\n", Instruction{Op: SLT, OpMode: B, AMode: DIRECT, A: 1, BMode: IMMEDIATE, B: 2}}, + {"SLT @ 1, # 2\n", Instruction{Op: SLT, OpMode: B, AMode: B_INDIRECT, A: 1, BMode: IMMEDIATE, B: 2}}, + {"SLT < 1, # 2\n", Instruction{Op: SLT, OpMode: B, AMode: B_DECREMENT, A: 1, BMode: IMMEDIATE, B: 2}}, + // JMP, JMN, JMZ, DJN, SPL {"JMP $ 1, # 2\n", Instruction{Op: JMP, OpMode: B, AMode: DIRECT, A: 1, BMode: IMMEDIATE, B: 2}}, {"JMP $ 1, $ 2\n", Instruction{Op: JMP, OpMode: B, AMode: DIRECT, A: 1, BMode: DIRECT, B: 2}}, diff --git a/parser.go b/parser.go index 44e56d5..ed05177 100644 --- a/parser.go +++ b/parser.go @@ -30,25 +30,6 @@ 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 tokenReader diff --git a/parser_test.go b/parser_test.go index 4c965f5..5b1d6c7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -183,6 +183,24 @@ func TestParserPositive(t *testing.T) { }, }, }, + { + input: "label:dat $0, $0\n", + output: []sourceLine{ + { + line: 1, + codeLine: 0, + labels: []string{"label"}, + typ: lineInstruction, + op: "dat", + amode: "$", + a: []token{{typ: tokNumber, val: "0"}}, + bmode: "$", + b: []token{{typ: tokNumber, val: "0"}}, + comment: "", + newlines: 1, + }, + }, + }, } runParserTests(t, "parser positive", testCases) diff --git a/symbol_scanner.go b/symbol_scanner.go index 89eef1b..f4dcea6 100644 --- a/symbol_scanner.go +++ b/symbol_scanner.go @@ -16,7 +16,7 @@ type symbolScanner struct { atEOF bool valBuf []token labelBuf []string - forLevel int + forSeen bool err error symbols map[string][]token @@ -52,17 +52,22 @@ func (p *symbolScanner) next() token { return retTok } -// run the preprocessor -func (p *symbolScanner) ScanInput() (map[string][]token, error) { +func ScanInput(lex tokenReader) (map[string][]token, bool, error) { + scanner := newSymbolScanner(lex) + return scanner.ScanInput() +} + +func (p *symbolScanner) ScanInput() (map[string][]token, bool, error) { for state := scanLine; state != nil; { state = state(p) } if p.err != nil { - return nil, p.err + return nil, false, p.err } - return p.symbols, nil + return p.symbols, p.forSeen, nil } +// consume the current nextToken and go to nextState unless EOF func (p *symbolScanner) consume(nextState scanStateFn) scanStateFn { p.next() if p.nextToken.typ == tokEOF { @@ -95,24 +100,13 @@ func scanLabels(p *symbolScanner) scanStateFn { opLower := strings.ToLower(p.nextToken.val) switch opLower { case "equ": - if p.forLevel == 0 { - p.valBuf = make([]token, 0) - return p.consume(scanEquValue) - } + p.valBuf = make([]token, 0) + return p.consume(scanEquValue) case "for": - p.forLevel++ - return scanConsumeLine - case "rof": - if p.forLevel > 0 { - p.forLevel-- - } - return scanConsumeLine + p.forSeen = true + return nil case "end": - if p.forLevel > 1 { - return scanConsumeLine - } else { - return nil - } + return nil default: return scanConsumeLine } diff --git a/symbol_scanner_test.go b/symbol_scanner_test.go index 8081ef4..1f30283 100644 --- a/symbol_scanner_test.go +++ b/symbol_scanner_test.go @@ -19,7 +19,7 @@ func runSymbolScannerTests(t *testing.T, cases []symbolScannerTestCase) { require.NotNil(t, tokens) scanner := newSymbolScanner(newBufTokenReader(tokens)) - symbols, err := scanner.ScanInput() + symbols, _, err := scanner.ScanInput() require.NoError(t, err) require.NotNil(t, symbols) diff --git a/test_files/.gitignore b/test_files/.gitignore new file mode 100644 index 0000000..a86f18d --- /dev/null +++ b/test_files/.gitignore @@ -0,0 +1,4 @@ +k88/ +k88c/ +k94/ +k94c/