v / examples / mini_calculator_recursive_descent.v
98 lines · 91 sloc · 1.94 KB · 3e4f9d7fa19341675380cb9c915d0527a518325b
Raw
1// Q: What's this?
2// A: A simple arithmetic calculator, similar to examples/mini_calculator.v,
3// but *without* an explicit stack. Instead, it re-uses the one from the host language.
4// It also demonstrates how to use textscanner.TextScanner from the V standart library.
5// See https://modules.vlang.io/strings.textscanner.html ,
6// and also https://en.wikipedia.org/wiki/Recursive_descent_parser .
7module main
8
9import os
10import strconv
11import strings.textscanner
12
13struct Parser {
14 textscanner.TextScanner
15}
16
17fn (mut p Parser) expr() !f64 {
18 mut result := p.term()!
19 for {
20 p.skip_whitespace()
21 c := p.peek_u8()
22 if c == `+` {
23 p.next()
24 result += p.term()!
25 } else if c == `-` {
26 p.next()
27 result -= p.term()!
28 } else {
29 break
30 }
31 }
32 return result
33}
34
35fn (mut p Parser) term() !f64 {
36 mut result := p.factor()!
37 for {
38 p.skip_whitespace()
39 c := p.peek_u8()
40 if c == `*` {
41 p.next()
42 result *= p.factor()!
43 } else if c == `/` {
44 p.next()
45 result /= p.factor()!
46 } else {
47 break
48 }
49 }
50 return result
51}
52
53fn (mut p Parser) factor() !f64 {
54 p.skip_whitespace()
55 c := p.peek_u8()
56 if c.is_digit() {
57 return p.number()
58 } else if c == `(` {
59 p.next()
60 result := p.expr()!
61 p.skip_whitespace()
62 if p.next() != `)` {
63 return error('Expected closing parenthesis')
64 }
65 return result
66 }
67 return error('Expected number or opening parenthesis')
68}
69
70fn (mut p Parser) number() !f64 {
71 start := p.pos
72 for {
73 c := p.peek_u8()
74 if c.is_digit() || c == `.` || c == `e` || c == `E` {
75 p.next()
76 continue
77 }
78 break
79 }
80 return strconv.atof64(p.input[start..p.pos]) or { error('Invalid number') }
81}
82
83println('Enter expressions to calculate, e.g. `2 * (5-1)` or `exit` to quit.')
84for i := 1; true; i++ {
85 input := os.input_opt('[${i}] ') or {
86 println('')
87 break
88 }.trim_space()
89 if input.to_upper() == 'EXIT' {
90 break
91 }
92 mut parser := Parser{textscanner.new(input)}
93 result := parser.expr() or {
94 println('Error: ${err}')
95 continue
96 }
97 println(result)
98}
99