pxasme/lexer.go

111 lines
2.0 KiB
Go

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
case "ret":
return RetInstrToken{}, nil
case "nop":
return NopInstrToken{}, 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)
}