blob: 15b4ceae15030bf7412307f666e2b7b3f20fb606 [file] [log] [blame]
Sketch🕴️b05c53f2026-02-28 19:13:36 +04001package parser
2
3import (
4 "fmt"
5 "matheval/ast"
6 "matheval/token"
7 "strconv"
8)
9
10// Parse converts a slice of tokens into an AST.
11// Returns an error for malformed expressions.
12func Parse(tokens []token.Token) (ast.Node, error) {
13 p := &parser{tokens: tokens}
14 node, err := p.expr()
15 if err != nil {
16 return nil, err
17 }
18 // Ensure all tokens have been consumed (only EOF remains).
19 if p.current().Type != token.EOF {
20 tok := p.current()
21 return nil, fmt.Errorf("unexpected token %v at position %d", tok.Type, tok.Pos)
22 }
23 return node, nil
24}
25
Sketch🕴️547feda2026-02-28 20:55:39 +040026// ParseLine parses a full REPL line, returning either a function definition
27// or an expression statement.
28func ParseLine(tokens []token.Token) (ast.Statement, error) {
29 if len(tokens) == 0 || tokens[0].Type == token.EOF {
30 return nil, fmt.Errorf("empty input")
31 }
32
33 // Detect function definition: look for Equals token.
34 // A function definition has the form: Ident LParen params RParen Equals body
35 if isFuncDef(tokens) {
36 return parseFuncDef(tokens)
37 }
38
39 // Otherwise, parse as expression.
40 node, err := Parse(tokens)
41 if err != nil {
42 return nil, err
43 }
44 return &ast.ExprStmt{Expr: node}, nil
45}
46
47// isFuncDef checks if the token stream looks like a function definition.
48// Pattern: Ident LParen ... RParen Equals ...
49func isFuncDef(tokens []token.Token) bool {
50 if len(tokens) < 5 {
51 return false
52 }
53 if tokens[0].Type != token.Ident {
54 return false
55 }
56 if tokens[1].Type != token.LParen {
57 return false
58 }
59 // Find matching RParen, then check for Equals.
60 depth := 0
61 for i := 1; i < len(tokens); i++ {
62 switch tokens[i].Type {
63 case token.LParen:
64 depth++
65 case token.RParen:
66 depth--
67 if depth == 0 {
68 // Next token must be Equals for this to be a func def.
69 if i+1 < len(tokens) && tokens[i+1].Type == token.Equals {
70 return true
71 }
72 return false
73 }
74 case token.EOF:
75 return false
76 }
77 }
78 return false
79}
80
81// parseFuncDef parses: Ident LParen param1, param2, ... RParen Equals body
82func parseFuncDef(tokens []token.Token) (*ast.FuncDef, error) {
83 p := &parser{tokens: tokens}
84
85 // Function name.
86 nameTok, err := p.expect(token.Ident)
87 if err != nil {
88 return nil, fmt.Errorf("expected function name: %w", err)
89 }
90 name := nameTok.Literal
91
92 // Opening paren.
93 if _, err := p.expect(token.LParen); err != nil {
94 return nil, fmt.Errorf("expected '(' after function name: %w", err)
95 }
96
97 // Parameters: comma-separated identifiers.
98 var params []string
99 if p.current().Type != token.RParen {
100 paramTok, err := p.expect(token.Ident)
101 if err != nil {
102 return nil, fmt.Errorf("expected parameter name: %w", err)
103 }
104 params = append(params, paramTok.Literal)
105
106 for p.current().Type == token.Comma {
107 p.advance() // consume comma
108 paramTok, err := p.expect(token.Ident)
109 if err != nil {
110 return nil, fmt.Errorf("expected parameter name after ',': %w", err)
111 }
112 params = append(params, paramTok.Literal)
113 }
114 }
115
116 // Closing paren.
117 if _, err := p.expect(token.RParen); err != nil {
118 return nil, fmt.Errorf("expected ')' after parameters: %w", err)
119 }
120
121 // Equals sign.
122 if _, err := p.expect(token.Equals); err != nil {
123 return nil, fmt.Errorf("expected '=' in function definition: %w", err)
124 }
125
126 // Body expression.
127 body, err := p.expr()
128 if err != nil {
129 return nil, fmt.Errorf("error in function body: %w", err)
130 }
131
132 // Ensure all tokens consumed.
133 if p.current().Type != token.EOF {
134 tok := p.current()
135 return nil, fmt.Errorf("unexpected token %v at position %d after function body", tok.Type, tok.Pos)
136 }
137
138 return &ast.FuncDef{
139 Name: name,
140 Params: params,
141 Body: body,
142 }, nil
143}
144
Sketch🕴️b05c53f2026-02-28 19:13:36 +0400145// parser holds the state for a single parse operation.
146type parser struct {
147 tokens []token.Token
148 pos int
149}
150
151// current returns the token at the current position.
152func (p *parser) current() token.Token {
153 if p.pos >= len(p.tokens) {
154 return token.Token{Type: token.EOF}
155 }
156 return p.tokens[p.pos]
157}
158
159// advance moves to the next token and returns the previous one.
160func (p *parser) advance() token.Token {
161 tok := p.current()
162 p.pos++
163 return tok
164}
165
166// expect consumes a token of the given type or returns an error.
167func (p *parser) expect(typ token.Type) (token.Token, error) {
168 tok := p.current()
169 if tok.Type != typ {
170 return tok, fmt.Errorf("expected %v but got %v at position %d", typ, tok.Type, tok.Pos)
171 }
172 p.advance()
173 return tok, nil
174}
175
176// expr → term (('+' | '-') term)*
177func (p *parser) expr() (ast.Node, error) {
178 left, err := p.term()
179 if err != nil {
180 return nil, err
181 }
182 for p.current().Type == token.Plus || p.current().Type == token.Minus {
183 op := p.advance()
184 right, err := p.term()
185 if err != nil {
186 return nil, err
187 }
188 left = &ast.BinaryExpr{
189 Op: op.Type,
190 Left: left,
191 Right: right,
192 }
193 }
194 return left, nil
195}
196
197// term → factor (('*' | '/') factor)*
198func (p *parser) term() (ast.Node, error) {
199 left, err := p.factor()
200 if err != nil {
201 return nil, err
202 }
203 for p.current().Type == token.Star || p.current().Type == token.Slash {
204 op := p.advance()
205 right, err := p.factor()
206 if err != nil {
207 return nil, err
208 }
209 left = &ast.BinaryExpr{
210 Op: op.Type,
211 Left: left,
212 Right: right,
213 }
214 }
215 return left, nil
216}
217
Sketch🕴️547feda2026-02-28 20:55:39 +0400218// factor → NUMBER | IDENT | IDENT '(' args ')' | '(' expr ')'
Sketch🕴️b05c53f2026-02-28 19:13:36 +0400219func (p *parser) factor() (ast.Node, error) {
220 tok := p.current()
221 switch tok.Type {
222 case token.Number:
223 p.advance()
224 val, err := strconv.ParseFloat(tok.Literal, 64)
225 if err != nil {
226 return nil, fmt.Errorf("invalid number %q at position %d: %w", tok.Literal, tok.Pos, err)
227 }
228 return &ast.NumberLit{Value: val}, nil
229
Sketch🕴️547feda2026-02-28 20:55:39 +0400230 case token.Ident:
231 p.advance()
232 // If followed by '(', this is a function call.
233 if p.current().Type == token.LParen {
234 p.advance() // consume '('
235 var args []ast.Node
236 if p.current().Type != token.RParen {
237 arg, err := p.expr()
238 if err != nil {
239 return nil, err
240 }
241 args = append(args, arg)
242 for p.current().Type == token.Comma {
243 p.advance() // consume ','
244 arg, err := p.expr()
245 if err != nil {
246 return nil, err
247 }
248 args = append(args, arg)
249 }
250 }
251 if _, err := p.expect(token.RParen); err != nil {
252 return nil, fmt.Errorf("expected ')' after function arguments at position %d", p.current().Pos)
253 }
254 return &ast.FuncCall{Name: tok.Literal, Args: args}, nil
255 }
256 // Otherwise, it's a variable reference.
257 return &ast.Ident{Name: tok.Literal}, nil
258
Sketch🕴️b05c53f2026-02-28 19:13:36 +0400259 case token.LParen:
260 p.advance() // consume '('
261 node, err := p.expr()
262 if err != nil {
263 return nil, err
264 }
265 if _, err := p.expect(token.RParen); err != nil {
266 return nil, fmt.Errorf("missing closing parenthesis at position %d", p.current().Pos)
267 }
268 return node, nil
269
270 default:
271 return nil, fmt.Errorf("unexpected token %v at position %d", tok.Type, tok.Pos)
272 }
273}