From c7ee45fc6465125b3504d106337857bc9553c75f Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Thu, 28 Nov 2024 21:17:24 +0200 Subject: [PATCH] strings.textscanner, examples: add TextScanner .skip_whitespace/0, .peek_u8/0, .peek_n_u8/0, add examples/mini_calculator_recursive_descent.v (#23001) --- examples/mini_calculator_recursive_descent.v | 102 +++++++++++++++++++ vlib/strings/textscanner/textscanner.v | 35 ++++++- vlib/strings/textscanner/textscanner_test.v | 31 ++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 examples/mini_calculator_recursive_descent.v diff --git a/examples/mini_calculator_recursive_descent.v b/examples/mini_calculator_recursive_descent.v new file mode 100644 index 000000000..04525a532 --- /dev/null +++ b/examples/mini_calculator_recursive_descent.v @@ -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) +} diff --git a/vlib/strings/textscanner/textscanner.v b/vlib/strings/textscanner/textscanner.v index c39acdb64..97091466b 100644 --- a/vlib/strings/textscanner/textscanner.v +++ b/vlib/strings/textscanner/textscanner.v @@ -7,7 +7,7 @@ pub struct TextScanner { pub: input string ilen int -mut: +pub mut: pos int // current position; pos is *always* kept in [0,ilen] } @@ -75,6 +75,19 @@ pub fn (ss &TextScanner) peek() int { 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 `-1` if it can't peek `n` characters ahead. // ts.peek_n(0) == ts.current() . @@ -87,6 +100,19 @@ pub fn (ss &TextScanner) peek_n(n int) int { 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. @[inline] pub fn (mut ss TextScanner) back() { @@ -152,3 +178,10 @@ pub fn (mut ss TextScanner) reset() { pub fn (mut ss TextScanner) goto_end() { 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() + } +} diff --git a/vlib/strings/textscanner/textscanner_test.v b/vlib/strings/textscanner/textscanner_test.v index ef91b42a4..c64b5f917 100644 --- a/vlib/strings/textscanner/textscanner_test.v +++ b/vlib/strings/textscanner/textscanner_test.v @@ -183,3 +183,34 @@ fn test_goto_end() { s.goto_end() 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 +} -- 2.39.5