224 lines
3.7 KiB
Go
224 lines
3.7 KiB
Go
package lexer
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"git.milar.in/milarin/bufr"
|
|
"git.milar.in/milarin/slices"
|
|
)
|
|
|
|
type Lexer struct {
|
|
src *bufr.Reader
|
|
Indent string
|
|
}
|
|
|
|
func New(r io.Reader) *Lexer {
|
|
return &Lexer{
|
|
src: bufr.New(r),
|
|
}
|
|
}
|
|
|
|
func (t *Lexer) Pos() bufr.Position {
|
|
index, line, column := t.src.Pos()
|
|
return bufr.Position{Index: index, Line: line, Column: column}
|
|
}
|
|
|
|
func (t *Lexer) Next() (*Token, error) {
|
|
rn, err := t.src.Rune()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rn2, err := t.src.Rune()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := t.src.UnreadRunes(2); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rn == '\t' || (rn == ' ' && rn2 == ' ') {
|
|
return t.parseIndent()
|
|
} else if rn == '#' {
|
|
return t.parseComment()
|
|
} else if slices.Contains(Separators, rn) {
|
|
return t.parseSeparator()
|
|
} else if slices.Contains(StringSeparators, rn) {
|
|
return t.parseStringLiteral()
|
|
} else if slices.Contains(Operators, rn) {
|
|
return t.parseOperator()
|
|
}
|
|
|
|
str, err := t.src.PeekStringUntil(bufr.OneOf(" \n"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if slices.Contains(Keywords, str) {
|
|
return t.parseKeyword()
|
|
}
|
|
|
|
return t.parseWord()
|
|
}
|
|
|
|
func (t *Lexer) parseComment() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
comment, err := t.src.StringUntil(bufr.IsNewLine)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := t.src.UnreadRune(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Token{
|
|
Type: TokenTypeComment,
|
|
Value: comment,
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
func (t *Lexer) parseKeyword() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
keyword, err := t.src.StringUntil(bufr.IsWhitespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := t.src.UnreadRune(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Token{
|
|
Type: TokenTypeKeyword,
|
|
Value: keyword,
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
func (t *Lexer) parseWord() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
word, err := t.src.StringUntil(bufr.IsWhitespace, bufr.Is('='))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := t.src.UnreadRune(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Token{
|
|
Type: TokenTypeWord,
|
|
Value: word,
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
func (t *Lexer) parseSeparator() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
rn, err := t.src.Rune()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Token{
|
|
Type: TokenTypeSeparator,
|
|
Value: string(rn),
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
func (t *Lexer) parseOperator() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
rn, err := t.src.Rune()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Token{
|
|
Type: TokenTypeOperator,
|
|
Value: string(rn),
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
func (t *Lexer) parseStringLiteral() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
startRn, err := t.src.Rune()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
literal, err := t.src.StringUntil(bufr.Is(startRn))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
literal = EscSeqReplacer.Replace(literal)
|
|
|
|
return &Token{
|
|
Type: TokenTypeWord,
|
|
Value: literal,
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
func (t *Lexer) parseIndent() (*Token, error) {
|
|
start := t.Pos()
|
|
|
|
// no indentation set yet
|
|
if t.Indent == "" {
|
|
str, err := t.src.StringWhile(bufr.OneOf("\t "))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := t.src.UnreadRune(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//fmt.Printf("indentation set to '%s'\n", EscSeqReplacer.Replace(str))
|
|
t.Indent = str
|
|
|
|
return &Token{
|
|
Type: TokenTypeIndent,
|
|
Value: str,
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|
|
|
|
for _, rn := range t.Indent {
|
|
ok, err := t.src.ExpectRune(bufr.Is(rn))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !ok {
|
|
return nil, fmt.Errorf("mixed indentation styles at (%d:%d)", start.Line, start.Column)
|
|
}
|
|
}
|
|
|
|
return &Token{
|
|
Type: TokenTypeIndent,
|
|
Value: t.Indent,
|
|
Start: start,
|
|
End: t.Pos(),
|
|
}, nil
|
|
}
|