Skip to content

Commit 10a25b1

Browse files
committed
parse-util
1 parent 1ecb35f commit 10a25b1

File tree

4 files changed

+122
-37
lines changed

4 files changed

+122
-37
lines changed

plugins/r/src/plugin.rs

+30-37
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use std::io::{Read, Seek};
99
use std::path::{Path, PathBuf};
1010
use std::time::Duration;
1111
use tmc_langs_framework::{
12-
nom::{branch, bytes, character, combinator, error::VerboseError, multi, sequence, IResult},
12+
nom::{branch, bytes, character, error::VerboseError, sequence, IResult},
1313
LanguagePlugin, TmcCommand, TmcError, {ExerciseDesc, RunResult, TestDesc},
1414
};
15-
use tmc_langs_util::file_util;
15+
use tmc_langs_util::{file_util, parse_util};
1616
use zip::ZipArchive;
1717

1818
#[derive(Default)]
@@ -143,13 +143,16 @@ impl LanguagePlugin for RPlugin {
143143
}
144144

145145
fn points_parser(i: &str) -> IResult<&str, Vec<&str>, VerboseError<&str>> {
146-
let mut test_parser = sequence::preceded(
146+
let test_parser = sequence::preceded(
147147
sequence::tuple((
148148
bytes::complete::tag("test"),
149149
character::complete::multispace0,
150150
character::complete::char('('),
151151
character::complete::multispace0,
152-
arg_parser,
152+
parse_util::string, // parses the first argument which should be a string
153+
character::complete::multispace0,
154+
character::complete::char(','),
155+
character::complete::multispace0,
153156
)),
154157
c_parser,
155158
);
@@ -163,48 +166,19 @@ impl LanguagePlugin for RPlugin {
163166
c_parser,
164167
);
165168

166-
// todo: currently cannot handle function calls with multiple parameters, probably not a problem
167-
fn arg_parser(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
168-
combinator::value(
169-
"",
170-
sequence::tuple((
171-
bytes::complete::take_till(|c: char| c == ','),
172-
character::complete::char(','),
173-
character::complete::multispace0,
174-
)),
175-
)(i)
176-
}
177-
178169
fn c_parser(i: &str) -> IResult<&str, Vec<&str>, VerboseError<&str>> {
179-
combinator::map(
170+
sequence::delimited(
180171
sequence::tuple((
181172
character::complete::char('c'),
182173
character::complete::multispace0,
183174
character::complete::char('('),
184175
character::complete::multispace0,
185-
multi::separated_list1(
186-
sequence::tuple((
187-
character::complete::multispace0,
188-
character::complete::char(','),
189-
character::complete::multispace0,
190-
)),
191-
string_parser,
192-
),
193-
character::complete::multispace0,
194-
character::complete::char(')'),
195176
)),
196-
|t| t.4,
197-
)(i)
198-
}
199-
200-
fn string_parser(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
201-
combinator::map(
177+
parse_util::comma_separated_strings,
202178
sequence::tuple((
203-
character::complete::char('"'),
204-
bytes::complete::is_not("\""),
205-
character::complete::char('"'),
179+
character::complete::multispace0,
180+
character::complete::char(')'),
206181
)),
207-
|r| str::trim(r.1),
208182
)(i)
209183
}
210184

@@ -556,4 +530,23 @@ etc
556530
let points = RPlugin::get_available_points(temp.path()).unwrap();
557531
assert_eq!(points, &["r1", "r2", "r3"]);
558532
}
533+
534+
#[test]
535+
fn parses_first_arg_with_comma_regression() {
536+
init();
537+
538+
let temp = tempfile::tempdir().unwrap();
539+
file_to(
540+
&temp,
541+
"tests/testthat/testExercise.R",
542+
r#"
543+
something
544+
test("some test, with a comma", c("r1", "r2", "r3"))
545+
etc
546+
"#,
547+
);
548+
549+
let points = RPlugin::get_available_points(temp.path()).unwrap();
550+
assert_eq!(points, &["r1", "r2", "r3"]);
551+
}
559552
}

tmc-langs-util/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0"
88
[dependencies]
99
fd-lock = "2"
1010
log = "0.4"
11+
nom = "6"
1112
once_cell = "1"
1213
serde = { version = "1", features = ["derive"] }
1314
tempfile = "3"

tmc-langs-util/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
pub mod error;
66
pub mod file_util;
77
pub mod notification_reporter;
8+
pub mod parse_util;
89
pub mod progress_reporter;
910

1011
pub use error::FileError;

tmc-langs-util/src/parse_util.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Contains parse functions that may be convenient for implementing language plugins.
2+
use nom::{branch, bytes, character, error::VerboseError, multi, sequence, IResult};
3+
4+
/// Parses a string delimited by double quotes.
5+
pub fn string(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
6+
sequence::delimited(
7+
character::complete::char('"'),
8+
bytes::complete::is_not("\""),
9+
character::complete::char('"'),
10+
)(i)
11+
}
12+
13+
/// Parses a string delimited by single quotes.
14+
pub fn string_single(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
15+
sequence::delimited(
16+
character::complete::char('\''),
17+
bytes::complete::is_not("'"),
18+
character::complete::char('\''),
19+
)(i)
20+
}
21+
22+
/// Parses a comma-separated list of double quote strings like "a", "b", "c".
23+
pub fn comma_separated_strings(i: &str) -> IResult<&str, Vec<&str>, VerboseError<&str>> {
24+
comma_separated_things(string, i)
25+
}
26+
27+
/// Parses a comma-separated list of single quote strings like 'a', 'b', 'c'.
28+
pub fn comma_separated_strings_single(i: &str) -> IResult<&str, Vec<&str>, VerboseError<&str>> {
29+
comma_separated_things(string_single, i)
30+
}
31+
32+
/// Parses a comma-separated list of mixed quote strings like 'a', "b", 'c'.
33+
pub fn comma_separated_strings_either(i: &str) -> IResult<&str, Vec<&str>, VerboseError<&str>> {
34+
comma_separated_things(branch::alt((string, string_single)), i)
35+
}
36+
37+
/// Parses a comma-separated list of things, thing being defined by the parser given to the function.
38+
fn comma_separated_things<'a>(
39+
thing_parser: impl FnMut(&'a str) -> IResult<&'a str, &'a str, VerboseError<&'a str>>,
40+
i: &'a str,
41+
) -> IResult<&str, Vec<&str>, VerboseError<&str>> {
42+
multi::separated_list1(
43+
sequence::delimited(
44+
character::complete::multispace0,
45+
character::complete::char(','),
46+
character::complete::multispace0,
47+
),
48+
thing_parser,
49+
)(i)
50+
}
51+
52+
#[cfg(test)]
53+
mod test {
54+
use super::*;
55+
56+
#[test]
57+
fn parses_string() {
58+
let (left, res) = string("\"abcd\"").unwrap();
59+
assert!(left.is_empty());
60+
assert_eq!(res, "abcd");
61+
}
62+
63+
#[test]
64+
fn parses_string_single() {
65+
let (left, res) = string_single("'abcd'").unwrap();
66+
assert!(left.is_empty());
67+
assert_eq!(res, "abcd");
68+
}
69+
70+
#[test]
71+
fn parses_comma_separated_strings() {
72+
let (left, res) = comma_separated_strings("\"abcd\", \"efgh\", \"hijk\"").unwrap();
73+
assert!(left.is_empty());
74+
assert_eq!(res, &["abcd", "efgh", "hijk"]);
75+
}
76+
77+
#[test]
78+
fn parses_comma_separated_strings_single() {
79+
let (left, res) = comma_separated_strings_single("'abcd', 'efgh', 'hijk'").unwrap();
80+
assert!(left.is_empty());
81+
assert_eq!(res, &["abcd", "efgh", "hijk"]);
82+
}
83+
84+
#[test]
85+
fn parses_comma_separated_strings_either() {
86+
let (left, res) = comma_separated_strings_either("'abcd', \"efgh\", 'hijk'").unwrap();
87+
assert!(left.is_empty());
88+
assert_eq!(res, &["abcd", "efgh", "hijk"]);
89+
}
90+
}

0 commit comments

Comments
 (0)