package main import ( "bufio" "fmt" "io" "strings" ) type lexer struct { r *bufio.Reader lineno int peek Token } func NewLexer(src io.Reader) *lexer { return &lexer{ r: bufio.NewReader(src), lineno: 0, } } func (l *lexer) Peek() (Token, error) { tok, err := l.Next() if err != nil { return nil, err } l.peek = tok return tok, nil } func (l *lexer) Next() (Token, error) { if l.peek != nil { ret := l.peek l.peek = nil return ret, nil } // l.lineno++ line, err := l.r.ReadString('\n') if err != nil { return nil, err } // Strip leading spaces line = strings.TrimLeft(line, " \t\r\n") // Strip trailing line-comments (;) line, _, _ = strings.Cut(line, `;`) if len(line) == 0 { // This line only contained comments // Continue to the next line return l.Next() } fields := strings.Fields(line) // FIXME commas!? switch strings.ToLower(fields[0]) { case "section": return SectionToken{fields[1]}, nil case "global": return LabelToken{strings.TrimRight(fields[1], `:`), true}, nil case "mov": for i, _ := range fields { fields[i] = strings.TrimRight(fields[i], `,`) } return MovInstrToken{fields[1:]}, nil case "syscall": return SyscallInstrToken{}, nil default: // If the field ends with `:`, it's a (local) label if strings.HasSuffix(fields[0], `:`) { return LabelToken{strings.TrimRight(fields[0], `:`), false}, nil } // If the field starts with `$`, it's a "variable" if strings.HasPrefix(fields[0], `$`) { // 1: = if fields[1] != `=` { return nil, fmt.Errorf("Invalid syntax at line %d (expected = in declaration)", l.lineno) } // 2: sizeclass // 3+++: literal initializer return DataVariableInstrToken{ VarName: fields[0][1:], Sizeclass: fields[2], Value: strings.Join(fields[3:], " "), // FIXME consecutive spaces are ruined }, nil } } return nil, fmt.Errorf("Unknown token '%s' at line %d", fields[0], l.lineno) }