mirror of
https://github.com/vlang/v.git
synced 2025-09-13 22:42:26 +03:00
strings.textscanner, examples: add TextScanner .skip_whitespace/0, .peek_u8/0, .peek_n_u8/0, add examples/mini_calculator_recursive_descent.v (#23001)
This commit is contained in:
parent
acad4c2810
commit
c7ee45fc64
3 changed files with 167 additions and 1 deletions
102
examples/mini_calculator_recursive_descent.v
Normal file
102
examples/mini_calculator_recursive_descent.v
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Q: What's this?
|
||||||
|
// A: A simple arithmetic calculator, similar to examples/mini_calculator.v,
|
||||||
|
// but *without* an explicit stack. Instead, it re-uses the one from the host language.
|
||||||
|
// It also demonstrates how to use textscanner.TextScanner from the V standart library.
|
||||||
|
// See https://modules.vlang.io/strings.textscanner.html ,
|
||||||
|
// and also https://en.wikipedia.org/wiki/Recursive_descent_parser .
|
||||||
|
module main
|
||||||
|
|
||||||
|
import os
|
||||||
|
import strconv
|
||||||
|
import strings.textscanner
|
||||||
|
|
||||||
|
struct Parser {
|
||||||
|
textscanner.TextScanner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Parser) parse_expression() !f64 {
|
||||||
|
mut result := p.parse_term()!
|
||||||
|
for {
|
||||||
|
p.skip_whitespace()
|
||||||
|
c := p.peek_u8()
|
||||||
|
if c == `+` {
|
||||||
|
p.next()
|
||||||
|
result += p.parse_term()!
|
||||||
|
} else if c == `-` {
|
||||||
|
p.next()
|
||||||
|
result -= p.parse_term()!
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Parser) parse_term() !f64 {
|
||||||
|
mut result := p.parse_factor()!
|
||||||
|
for {
|
||||||
|
p.skip_whitespace()
|
||||||
|
c := p.peek_u8()
|
||||||
|
if c == `*` {
|
||||||
|
p.next()
|
||||||
|
result *= p.parse_factor()!
|
||||||
|
} else if c == `/` {
|
||||||
|
p.next()
|
||||||
|
result /= p.parse_factor()!
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Parser) parse_factor() !f64 {
|
||||||
|
p.skip_whitespace()
|
||||||
|
c := p.peek_u8()
|
||||||
|
if c.is_digit() {
|
||||||
|
return p.parse_number()
|
||||||
|
} else if c == `(` {
|
||||||
|
p.next()
|
||||||
|
result := p.parse_expression()!
|
||||||
|
p.skip_whitespace()
|
||||||
|
if p.next() != `)` {
|
||||||
|
return error('Expected closing parenthesis')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return error('Expected number or opening parenthesis')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut p Parser) parse_number() !f64 {
|
||||||
|
start := p.pos
|
||||||
|
for {
|
||||||
|
c := p.peek_u8()
|
||||||
|
if c.is_digit() || c == `.` || c == `e` || c == `E` {
|
||||||
|
p.next()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
num_str := p.input[start..p.pos]
|
||||||
|
return strconv.atof64(num_str) or { return error('Invalid number') }
|
||||||
|
}
|
||||||
|
|
||||||
|
println('Please enter the expression you want to calculate, e.g. `2 * (5-1)` .')
|
||||||
|
println("Enter 'exit' or 'EXIT' to quit.")
|
||||||
|
mut expr_count := 0
|
||||||
|
for {
|
||||||
|
expr_count++
|
||||||
|
input := os.input_opt('[${expr_count}] ') or {
|
||||||
|
println('')
|
||||||
|
break
|
||||||
|
}.trim_space()
|
||||||
|
if input in ['exit', 'EXIT'] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mut parser := Parser{textscanner.new(input)}
|
||||||
|
result := parser.parse_expression() or {
|
||||||
|
println('Error: ${err}')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
println(result)
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ pub struct TextScanner {
|
||||||
pub:
|
pub:
|
||||||
input string
|
input string
|
||||||
ilen int
|
ilen int
|
||||||
mut:
|
pub mut:
|
||||||
pos int // current position; pos is *always* kept in [0,ilen]
|
pos int // current position; pos is *always* kept in [0,ilen]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,19 @@ pub fn (ss &TextScanner) peek() int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// peek_u8 returns the *next* character code from the input text, as a byte/u8.
|
||||||
|
// unlike `next()`, `peek_u8()` does not change the state of the scanner.
|
||||||
|
// Note: peek_u8 returns `0`, if it can't peek the next character.
|
||||||
|
// Note: use `peek()`, instead of `peek_u8()`, if your input itself can
|
||||||
|
// legitimately contain bytes with value `0`.
|
||||||
|
@[direct_array_access; inline]
|
||||||
|
pub fn (ss &TextScanner) peek_u8() u8 {
|
||||||
|
if ss.pos < ss.ilen {
|
||||||
|
return ss.input[ss.pos]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// peek_n returns the character code from the input text at position + `n`.
|
// peek_n returns the character code from the input text at position + `n`.
|
||||||
// peek_n returns `-1` if it can't peek `n` characters ahead.
|
// peek_n returns `-1` if it can't peek `n` characters ahead.
|
||||||
// ts.peek_n(0) == ts.current() .
|
// ts.peek_n(0) == ts.current() .
|
||||||
|
@ -87,6 +100,19 @@ pub fn (ss &TextScanner) peek_n(n int) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// peek_n_u8 returns the character code from the input text, at position + `n`,
|
||||||
|
// as a byte/u8.
|
||||||
|
// Note: peek_n_u8 returns `0`, if it can't peek the next character.
|
||||||
|
// Note: use `peek_n()`, instead of `peek_n_u8()`, if your input itself can
|
||||||
|
// legitimately contain bytes with value `0`.
|
||||||
|
@[direct_array_access; inline]
|
||||||
|
pub fn (ss &TextScanner) peek_n_u8(n int) u8 {
|
||||||
|
if ss.pos + n < ss.ilen {
|
||||||
|
return ss.input[ss.pos + n]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// back goes back one character from the current scanner position.
|
// back goes back one character from the current scanner position.
|
||||||
@[inline]
|
@[inline]
|
||||||
pub fn (mut ss TextScanner) back() {
|
pub fn (mut ss TextScanner) back() {
|
||||||
|
@ -152,3 +178,10 @@ pub fn (mut ss TextScanner) reset() {
|
||||||
pub fn (mut ss TextScanner) goto_end() {
|
pub fn (mut ss TextScanner) goto_end() {
|
||||||
ss.pos = ss.ilen
|
ss.pos = ss.ilen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip_whitespace advances the scanner pass any space characters in the input.
|
||||||
|
pub fn (mut ss TextScanner) skip_whitespace() {
|
||||||
|
for ss.ilen - ss.pos > 0 && ss.peek_u8().is_space() {
|
||||||
|
ss.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -183,3 +183,34 @@ fn test_goto_end() {
|
||||||
s.goto_end()
|
s.goto_end()
|
||||||
assert s.current() == `c`
|
assert s.current() == `c`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_skip_whitespace() {
|
||||||
|
mut s := textscanner.new('abc d \n xyz')
|
||||||
|
assert s.current() == -1
|
||||||
|
assert s.next() == `a`
|
||||||
|
assert s.next() == `b`
|
||||||
|
assert s.next() == `c`
|
||||||
|
s.skip_whitespace()
|
||||||
|
assert s.next() == `d`
|
||||||
|
s.skip_whitespace()
|
||||||
|
assert s.next() == `x`
|
||||||
|
assert s.next() == `y`
|
||||||
|
assert s.next() == `z`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_peek_u8() {
|
||||||
|
mut s := textscanner.new('abc')
|
||||||
|
assert s.peek_u8() == `a`
|
||||||
|
assert !s.peek_u8().is_digit()
|
||||||
|
assert s.next() == `a`
|
||||||
|
assert s.peek_u8() == `b`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_peek_n_u8() {
|
||||||
|
mut s := textscanner.new('abc')
|
||||||
|
assert s.peek_n_u8(0) == `a`
|
||||||
|
assert s.peek_n_u8(1) == `b`
|
||||||
|
assert s.peek_n_u8(2) == `c`
|
||||||
|
assert s.peek_n_u8(3) == 0
|
||||||
|
assert s.peek_n_u8(4) == 0
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue