Skip to content

Commit 0406b9d

Browse files
authored
Merge pull request #30 from erkkikeranen/parse-negative-int
parse negative int, with reader unit tests
2 parents f33a6d7 + 704809e commit 0406b9d

File tree

3 files changed

+247
-9
lines changed

3 files changed

+247
-9
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,6 @@ Session.vim
6060
.netrwhist
6161
*~
6262
# auto-generated tag files
63-
tags
63+
tags
64+
65+
.idea

.idea/.gitignore

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/reader.rs

Lines changed: 242 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
//! not; since this is about being a 'free-er' Clojure, especially since it can't compete with it in raw
99
//! power, neither speed or ecosystem, it might be worth it to leave in reader macros.
1010
11-
use nom::{
12-
branch::alt, bytes::complete::tag, map, sequence::preceded, take_until, terminated, IResult,
13-
};
11+
use nom::{branch::alt, bytes::complete::tag, map, sequence::preceded, take_until, terminated, IResult, Needed};
1412

1513
use crate::maps::MapEntry;
1614
use crate::persistent_list::ToPersistentList;
@@ -111,6 +109,12 @@ fn is_non_numeric_identifier_char(chr: char) -> bool {
111109
chr.is_alphabetic() || "|?<>+-_=^%&$*!".contains(chr)
112110
}
113111

112+
/// Returns true if given character is a minus character
113+
/// - `-`,
114+
fn is_minus_char(chr: char) -> bool {
115+
chr == '-'
116+
}
117+
114118
/// Parses valid Clojure identifiers
115119
/// Example Successes: ab, cat, -12+3, |blah|, <well>
116120
/// Example Failures: 'a, 12b, ,cat
@@ -140,12 +144,25 @@ pub fn symbol_parser(input: &str) -> IResult<&str, Symbol> {
140144
identifier_parser(input).map(|(rest_input, name)| (rest_input, Symbol::intern(&name)))
141145
}
142146

143-
// @TODO add negatives
144147
/// Parses valid integers
145148
/// Example Successes: 1, 2, 4153, -12421
149+
///
150+
///
146151
pub fn integer_parser(input: &str) -> IResult<&str, i32> {
147-
named!(integer_lexer<&str, &str>, take_while1!(|c: char| c.is_digit(10)));
148-
152+
named!(integer_sign<&str, &str>,
153+
map!(
154+
opt!(take_while_m_n!(1, 1, is_minus_char)),
155+
|maybe_minus| maybe_minus.unwrap_or("")
156+
)
157+
);
158+
named!(integer_tail<&str, &str>, take_while1!(|c: char| c.is_digit(10)));
159+
named!(integer_lexer <&str, String>,
160+
do_parse!(
161+
sign: integer_sign >>
162+
rest_input: integer_tail >>
163+
(format!("{}{}",sign,rest_input))
164+
)
165+
);
149166
integer_lexer(input).map(|(rest, digits)| (rest, digits.parse().unwrap()))
150167
}
151168
// Currently used to create 'try_readers', which are readers (or
@@ -170,6 +187,7 @@ pub fn to_value_parser<I, O: ToValue>(
170187
/// 1 => Value::I32(1),
171188
/// 5 => Value::I32(5),
172189
/// 1231415 => Value::I32(1231415)
190+
/// -2 => Value::I32(-2)
173191
/// Example Failures:
174192
/// 1.5, 7.1321 , 1423152621625226126431525
175193
pub fn try_read_i32(input: &str) -> IResult<&str, Value> {
@@ -208,7 +226,7 @@ pub fn try_read_string(input: &str) -> IResult<&str, Value> {
208226
}
209227

210228
// @TODO Perhaps generalize this, or even generalize it as a reader macro
211-
/// Tries to parse &str into Value::PersistentListMap, or some other Value::..Map
229+
/// Tries to parse &str into Value::PersistentListMap, or some other Value::..Map
212230
/// Example Successes:
213231
/// {:a 1} => Value::PersistentListMap {PersistentListMap { MapEntry { :a, 1} .. ]})
214232
pub fn try_read_map(input: &str) -> IResult<&str, Value> {
@@ -282,8 +300,8 @@ pub fn try_read(input: &str) -> IResult<&str, Value> {
282300
alt((
283301
try_read_map,
284302
try_read_string,
285-
try_read_symbol,
286303
try_read_i32,
304+
try_read_symbol,
287305
try_read_list,
288306
try_read_vector,
289307
)),
@@ -314,3 +332,219 @@ fn consume_clojure_whitespaces(input: &str) -> IResult<&str, ()> {
314332
fn is_clojure_whitespace(c: char) -> bool {
315333
c.is_whitespace() || c == ','
316334
}
335+
336+
#[cfg(test)]
337+
mod tests {
338+
339+
mod first_char_tests {
340+
use crate::reader::first_char;
341+
342+
#[test]
343+
fn first_char_in_single_char_string() {
344+
assert_eq!('s', first_char("s"));
345+
}
346+
347+
#[test]
348+
fn first_char_in_multi_char_string() {
349+
assert_eq!('a', first_char("ab"));
350+
}
351+
352+
#[test]
353+
#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
354+
fn first_char_in_empty_string_panics() {
355+
first_char("");
356+
}
357+
}
358+
359+
mod cons_str_tests {
360+
use crate::reader::cons_str;
361+
362+
#[test]
363+
fn concatenates_char_to_str_beginning() {
364+
assert_eq!("str", cons_str('s', "tr"));
365+
}
366+
}
367+
368+
mod identifier_parser_tests {
369+
use crate::reader::identifier_parser;
370+
371+
#[test]
372+
fn identifier_parser_parses_valid_identifier() {
373+
assert_eq!(Some((" this", String::from("input->output?"))), identifier_parser("input->output? this").ok());
374+
}
375+
376+
#[test]
377+
fn identifier_parser_does_not_parse_valid_identifier() {
378+
assert_eq!(None, identifier_parser("1input->output? this").ok());
379+
}
380+
381+
#[test]
382+
fn identifier_parser_does_not_parse_empty_input() {
383+
assert_eq!(None, identifier_parser("").ok());
384+
}
385+
}
386+
387+
mod symbol_parser_tests {
388+
use crate::reader::symbol_parser;
389+
use crate::symbol::Symbol;
390+
391+
#[test]
392+
fn identifier_parser_parses_valid_identifier() {
393+
assert_eq!(Some((" this", Symbol { name: String::from("input->output?")})), symbol_parser("input->output? this").ok());
394+
}
395+
396+
#[test]
397+
fn identifier_parser_does_not_parse_valid_identifier() {
398+
assert_eq!(None, symbol_parser("1input->output? this").ok());
399+
}
400+
401+
#[test]
402+
fn identifier_parser_does_not_parse_empty_input() {
403+
assert_eq!(None, symbol_parser("").ok());
404+
}
405+
}
406+
407+
mod integer_parser_tests {
408+
use crate::reader::{integer_parser, debug_try_read};
409+
410+
#[test]
411+
fn integer_parser_parses_integer_one() {
412+
let s = "1 ";
413+
assert_eq!(Some((" ", 1)), integer_parser(s).ok());
414+
}
415+
416+
#[test]
417+
fn integer_parser_parses_integer_zero() {
418+
let s = "0 ";
419+
assert_eq!(Some((" ", 0)), integer_parser(s).ok());
420+
}
421+
422+
#[test]
423+
fn integer_parser_parses_integer_negative_one() {
424+
let s = "-1 ";
425+
assert_eq!(Some((" ", -1)), integer_parser(s).ok());
426+
}
427+
428+
#[test]
429+
//#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }")]
430+
fn integer_parser_parses_and_fails() {
431+
let s = "-1-2 ";
432+
assert_eq!(Some(("-2 ", -1)), integer_parser(s).ok());
433+
}
434+
435+
}
436+
437+
mod try_read_symbol_tests {
438+
use crate::value::Value;
439+
use crate::symbol::Symbol;
440+
use crate::reader::try_read_symbol;
441+
442+
#[test]
443+
fn try_read_minus_as_valid_symbol_test() {
444+
assert_eq!(Value::Symbol(Symbol { name: String::from("-")}) , try_read_symbol("- ").unwrap().1);
445+
}
446+
}
447+
448+
mod try_read_tests {
449+
use crate::reader::try_read;
450+
use crate::value::{Value};
451+
use crate::symbol::Symbol;
452+
use crate::persistent_list_map;
453+
use crate::persistent_list;
454+
use crate::persistent_vector;
455+
use crate::value::Value::{PersistentListMap, PersistentList, PersistentVector};
456+
use crate::maps::MapEntry;
457+
use std::rc::Rc;
458+
459+
#[test]
460+
fn try_read_empty_map_test() {
461+
assert_eq!(PersistentListMap(persistent_list_map::PersistentListMap::Empty), try_read("{} ").ok().unwrap().1);
462+
}
463+
464+
#[test]
465+
fn try_read_string_test() {
466+
assert_eq!(Value::String(String::from("a string")), try_read("\"a string\" ").ok().unwrap().1);
467+
}
468+
469+
#[test]
470+
fn try_read_int_test() {
471+
assert_eq!(Value::I32(1), try_read("1 ").ok().unwrap().1);
472+
473+
}
474+
475+
#[test]
476+
fn try_read_negative_int_test() {
477+
assert_eq!(Value::I32(-1), try_read("-1 ").ok().unwrap().1);
478+
}
479+
480+
#[test]
481+
fn try_read_negative_int_with_second_dash_test() {
482+
assert_eq!(Value::I32(-1), try_read("-1-2 ").ok().unwrap().1);
483+
}
484+
485+
#[test]
486+
fn try_read_valid_symbol_test() {
487+
assert_eq!(Value::Symbol(Symbol { name: String::from("my-symbol")}) , try_read("my-symbol ").ok().unwrap().1);
488+
}
489+
490+
#[test]
491+
fn try_read_minus_as_valid_symbol_test() {
492+
assert_eq!(Value::Symbol(Symbol { name: String::from("-")}) , try_read("- ").ok().unwrap().1);
493+
}
494+
495+
#[test]
496+
fn try_read_minus_prefixed_as_valid_symbol_test() {
497+
assert_eq!(Value::Symbol(Symbol { name: String::from("-prefixed")}) , try_read("-prefixed ").ok().unwrap().1);
498+
}
499+
500+
#[test]
501+
fn try_read_empty_list_test() {
502+
assert_eq!(PersistentList(persistent_list::PersistentList::Empty), try_read("() ").ok().unwrap().1);
503+
}
504+
505+
#[test]
506+
fn try_read_empty_vector_test() {
507+
assert_eq!(PersistentVector(persistent_vector::PersistentVector { vals: [].to_vec() }), try_read("[] ").ok().unwrap().1);
508+
}
509+
510+
511+
}
512+
513+
mod consume_clojure_whitespaces_tests {
514+
use crate::reader::consume_clojure_whitespaces;
515+
#[test]
516+
fn consume_whitespaces_from_input() {
517+
let s = ", ,, ,1, 2, 3, 4 5,,6 ";
518+
assert_eq!(Some(("1, 2, 3, 4 5,,6 ", ())), consume_clojure_whitespaces(&s).ok());
519+
}
520+
#[test]
521+
fn consume_whitespaces_from_empty_input() {
522+
let s = "";
523+
assert_eq!(None, consume_clojure_whitespaces(&s).ok());
524+
}
525+
#[test]
526+
fn consume_whitespaces_from_input_no_whitespace() {
527+
let s = "1, 2, 3";
528+
assert_eq!(Some(("1, 2, 3", ())), consume_clojure_whitespaces(&s).ok());
529+
}
530+
}
531+
532+
mod is_clojure_whitespace_tests {
533+
use crate::reader::is_clojure_whitespace;
534+
#[test]
535+
fn comma_is_clojure_whitespace() {
536+
assert_eq!(true, is_clojure_whitespace(','));
537+
}
538+
539+
#[test]
540+
fn unicode_whitespace_is_clojure_whitespace() {
541+
assert_eq!(true, is_clojure_whitespace(' '));
542+
}
543+
544+
#[test]
545+
fn character_is_not_clojure_whitespace() {
546+
assert_eq!(false, is_clojure_whitespace('a'));
547+
}
548+
}
549+
550+
}

0 commit comments

Comments
 (0)