blob: 0914b72d96f9817d9487445bc4594942d41781b1 [file] [log] [blame]
Sketch🕴️cdbb1892026-02-28 19:10:35 +04001package lexer
2
3import (
4 "fmt"
5 "matheval/token"
6)
7
8// Tokenize converts an input string into a slice of tokens.
9// Returns an error if the input contains invalid characters.
10func Tokenize(input string) ([]token.Token, error) {
11 var tokens []token.Token
12 i := 0
13
14 for i < len(input) {
15 ch := input[i]
16
17 // Skip whitespace.
18 if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
19 i++
20 continue
21 }
22
23 // Number: digits and dots.
24 if isDigit(ch) || ch == '.' {
25 start := i
26 hasDot := false
27 for i < len(input) && (isDigit(input[i]) || input[i] == '.') {
28 if input[i] == '.' {
29 if hasDot {
30 break // second dot ends this number
31 }
32 hasDot = true
33 }
34 i++
35 }
36 tokens = append(tokens, token.Token{
37 Type: token.Number,
38 Literal: input[start:i],
39 Pos: start,
40 })
41 continue
42 }
43
44 // Single-character tokens.
45 var typ token.Type
46 switch ch {
47 case '+':
48 typ = token.Plus
49 case '-':
50 typ = token.Minus
51 case '*':
52 typ = token.Star
53 case '/':
54 typ = token.Slash
55 case '(':
56 typ = token.LParen
57 case ')':
58 typ = token.RParen
59 default:
60 return nil, fmt.Errorf("unexpected character %q at position %d", string(ch), i)
61 }
62
63 tokens = append(tokens, token.Token{
64 Type: typ,
65 Literal: string(ch),
66 Pos: i,
67 })
68 i++
69 }
70
71 // Always append EOF.
72 tokens = append(tokens, token.Token{
73 Type: token.EOF,
74 Literal: "",
75 Pos: i,
76 })
77
78 return tokens, nil
79}
80
81func isDigit(ch byte) bool {
82 return ch >= '0' && ch <= '9'
83}