package saccharine import ( "errors" "fmt" "unicode" "git.maximhutz.com/max/lambda/pkg/iterator" "git.maximhutz.com/max/lambda/pkg/saccharine/token" "git.maximhutz.com/max/lambda/pkg/trace" ) // isVariables determines whether a rune can be a valid variable. func isVariable(r rune) bool { return unicode.IsLetter(r) || unicode.IsNumber(r) } func parseRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, error) { i2 := i.Copy() if r, err := i2.Next(); err != nil { return r, err } else if !expected(r) { return r, fmt.Errorf("got unexpected rune %v'", r) } else { i.Sync(i2) return r, nil } } // Pulls the next token from an iterator over runes. If it cannot, it will // return nil. If an error occurs, it will return that. func getToken(i *iterator.Iterator[rune]) (*token.Token, error) { index := i.Index() if i.Done() { return nil, nil } letter, err := i.Next() if err != nil { return nil, trace.WrapError(fmt.Errorf("cannot produce next token"), err) } switch { case letter == '(': return token.NewOpenParen(index), nil case letter == ')': return token.NewCloseParen(index), nil case letter == '.': return token.NewDot(index), nil case letter == '\\': return token.NewSlash(index), nil case letter == '\n': return token.NewNewline(index), nil case unicode.IsSpace(letter): return nil, nil case isVariable(letter): atom := []rune{letter} for { if r, err := parseRune(i, isVariable); err != nil { break } else { atom = append(atom, r) } } return token.NewAtom(string(atom), index), nil } return nil, fmt.Errorf("unknown character '%v'", string(letter)) } // Parses a list of runes into tokens. All error encountered are returned, as well. func GetTokens(input string) ([]token.Token, error) { i := iterator.Of([]rune(input)) tokens := []token.Token{} errorList := []error{} for !i.Done() { token, err := getToken(i) if err != nil { errorList = append(errorList, err) } else if token != nil { tokens = append(tokens, *token) } } return tokens, errors.Join(errorList...) }