some progress + tests

This commit is contained in:
Kat Marchán 2020-12-12 17:51:18 -08:00
parent 100a736145
commit a316f6d2ef
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
3 changed files with 154 additions and 25 deletions

View File

@ -1,7 +1,7 @@
use nom::error::{ContextError, ParseError};
use thiserror::Error;
#[derive(Debug, Clone, Error)]
#[derive(Debug, Clone, Eq, PartialEq, Error)]
#[error("Error parsing document. {kind}")]
pub struct Error {
pub input: String,
@ -9,7 +9,7 @@ pub struct Error {
pub kind: ErrorKind,
}
#[derive(Debug, Clone, Error)]
#[derive(Debug, Clone, Eq, PartialEq, Error)]
pub enum ErrorKind {
#[error("Failed to parse {0} component of semver string.")]
Context(&'static str),
@ -19,7 +19,7 @@ pub enum ErrorKind {
Other,
}
#[derive(Debug)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct NodeParseError<I> {
pub(crate) input: I,
pub(crate) context: Option<&'static str>,

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub name: String,
pub values: Vec<NodeValue>,
@ -8,5 +8,11 @@ pub struct Node {
pub children: Vec<Node>,
}
#[derive(Debug, Clone)]
pub enum NodeValue {}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeValue {
Int(i64),
Float(f64),
String(String),
Boolean(bool),
Null,
}

View File

@ -1,14 +1,14 @@
use std::collections::HashMap;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{alpha1, alphanumeric1, one_of};
use nom::combinator::{map, not, opt, recognize};
use nom::bytes::complete::{is_not, tag, take_until};
use nom::character::complete::{alpha1, alphanumeric1, anychar, one_of};
use nom::combinator::{eof, map, opt, recognize, value};
use nom::multi::{many0, many1};
use nom::sequence::{delimited, pair, preceded};
use nom::IResult;
use crate::error::{ErrorKind, NodeParseError};
use crate::error::NodeParseError;
use crate::node::{Node, NodeValue};
/// `document := linespace* (node (newline document)?)?`
@ -71,19 +71,72 @@ fn identifier(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
}
fn node_arg(input: &str) -> IResult<&str, NodeArg, NodeParseError<&str>> {
todo!()
alt((
map(property, |(key, val)| NodeArg::Property(key, val)),
map(node_value, NodeArg::Value),
))(input)
}
fn node_children(input: &str) -> IResult<&str, Vec<Node>, NodeParseError<&str>> {
/// `prop := identifier '=' value`
fn property(input: &str) -> IResult<&str, (&str, NodeValue), NodeParseError<&str>> {
let (input, key) = identifier(input)?;
let (input, _) = tag("=")(input)?;
let (input, val) = node_value(input)?;
Ok((input, (key, val)))
}
/// `value := string | raw_string | number | boolean | 'null'`
fn node_value(input: &str) -> IResult<&str, NodeValue, NodeParseError<&str>> {
alt((
map(string, |s| NodeValue::String(s.into())),
map(raw_string, |s| NodeValue::String(s.into())),
number,
boolean,
value(NodeValue::Null, tag("null")),
))(input)
}
fn node_children(_input: &str) -> IResult<&str, Vec<Node>, NodeParseError<&str>> {
todo!()
}
// TODO: This should be much more specific about what escapes are allowed.
/// `string := '"' ('\\' ["\\] | [^"])* '"'`
fn string(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
fn string(_input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
todo!()
}
/// `raw-string := 'r' raw-string-hash`
fn raw_string(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
preceded(tag("r"), raw_string_hash)(input)
}
/// `raw-string-hash := '#' raw-string-hash '#' | raw-string-quotes`
fn raw_string_hash(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
alt((
delimited(tag("#"), raw_string_hash, tag("#")),
raw_string_quotes,
))(input)
}
/// `raw-string-quotes := '"' .* '"'`
fn raw_string_quotes(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
delimited(tag("\""), recognize(many0(anychar)), tag("\""))(input)
}
/// `number := decimal | hex | octal | binary`
fn number(_input: &str) -> IResult<&str, NodeValue, NodeParseError<&str>> {
todo!()
}
/// `boolean := 'true' | 'false'`
fn boolean(input: &str) -> IResult<&str, NodeValue, NodeParseError<&str>> {
alt((
value(NodeValue::Boolean(true), tag("true")),
value(NodeValue::Boolean(false), tag("false")),
))(input)
}
/// `node-space := ws* escline ws* | ws+`
fn node_space(input: &str) -> IResult<&str, (), NodeParseError<&str>> {
alt((
@ -92,37 +145,107 @@ fn node_space(input: &str) -> IResult<&str, (), NodeParseError<&str>> {
))(input)
}
/// `single-line-comment := '//' ('\r' [^\n] | [^\r\n])* newline`
fn single_line_comment(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
/// `single-line-comment := '//' ('\r' [^\n] | [^\r\n])* (newline | eof)`
fn single_line_comment(input: &str) -> IResult<&str, (), NodeParseError<&str>> {
let (input, _) = tag("//")(input)?;
let (input, comment) = recognize(many0(alt((
preceded(tag("\r"), not(tag("\n"))),
not(tag("\r\n")),
))))(input)?;
let (input, _) = newline(input)?;
Ok((input, comment))
let (input, _) = alt((take_until("\r\n"), is_not("\n")))(input)?;
let (input, _) = alt((newline, eof))(input)?;
Ok((input, ()))
}
/// `multi-line-comment := '/*' ('*' [^\/] | [^*])* '*/'`
fn multi_line_comment(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
delimited(tag("/*"), recognize(many0(not(tag("*/")))), tag("*/"))(input)
fn multi_line_comment(input: &str) -> IResult<&str, (), NodeParseError<&str>> {
delimited(tag("/*"), value((), take_until("*/")), tag("*/"))(input)
}
/// `escline := '\\' ws* (single-line-comment | newline)`
fn escline(input: &str) -> IResult<&str, (), NodeParseError<&str>> {
let (input, _) = tag("\\")(input)?;
let (input, _) = many0(whitespace)(input)?;
let (input, _) = alt((single_line_comment, newline))(input)?;
let (input, _) = alt((recognize(single_line_comment), newline))(input)?;
Ok((input, ()))
}
/// `ws := bom | ' ' | '\t' | multi-line-comment`
fn whitespace(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
// TODO: bom?
alt((/*bom,*/ tag(" "), tag("\t"), multi_line_comment))(input)
alt((
/*bom,*/ tag(" "),
tag("\t"),
recognize(multi_line_comment),
))(input)
}
/// `newline := ('\r' '\n') | '\n'`
fn newline(input: &str) -> IResult<&str, &str, NodeParseError<&str>> {
alt((tag("\r\n"), tag("\n")))(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boolean() {
assert_eq!(boolean("true"), Ok(("", NodeValue::Boolean(true))));
assert_eq!(boolean("false"), Ok(("", NodeValue::Boolean(false))));
assert!(boolean("blah").is_err());
}
#[test]
fn test_node_space() {
assert_eq!(node_space(" "), Ok(("", ())));
assert_eq!(node_space("\t "), Ok(("", ())));
assert_eq!(node_space("\t \\ // hello\n "), Ok(("", ())));
assert!(node_space("blah").is_err());
}
#[test]
fn test_single_line_comment() {
assert_eq!(single_line_comment("//hello"), Ok(("", ())));
assert_eq!(single_line_comment("// \thello"), Ok(("", ())));
assert_eq!(single_line_comment("//hello\n"), Ok(("", ())));
assert_eq!(single_line_comment("//hello\r\n"), Ok(("", ())));
assert_eq!(single_line_comment("//hello\n\r"), Ok(("\r", ())));
assert_eq!(single_line_comment("//hello\rworld"), Ok(("", ())));
}
#[test]
fn test_multi_line_comment() {
assert_eq!(multi_line_comment("/*hello*/"), Ok(("", ())));
assert_eq!(multi_line_comment("/*hello*/\n"), Ok(("\n", ())));
assert_eq!(multi_line_comment("/*\nhello\r\n*/"), Ok(("", ())));
assert_eq!(multi_line_comment("/*\nhello** /\n*/"), Ok(("", ())));
assert_eq!(multi_line_comment("/**\nhello** /\n*/"), Ok(("", ())));
assert_eq!(multi_line_comment("/*hello*/world"), Ok(("world", ())));
}
#[test]
fn test_escline() {
assert_eq!(escline("\\\nfoo"), Ok(("foo", ())));
assert_eq!(escline("\\\n foo"), Ok((" foo", ())));
assert_eq!(escline("\\ \t \nfoo"), Ok(("foo", ())));
assert_eq!(escline("\\ // test \nfoo"), Ok(("foo", ())));
assert_eq!(escline("\\ // test \n foo"), Ok((" foo", ())));
}
#[test]
fn test_whitespace() {
assert_eq!(whitespace(" "), Ok(("", " ")));
assert_eq!(whitespace("\t"), Ok(("", "\t")));
assert_eq!(
whitespace("/* \nfoo\r\n */ etc"),
Ok((" etc", "/* \nfoo\r\n */"))
);
assert!(whitespace("hi").is_err())
}
#[test]
fn test_newline() {
assert_eq!(newline("\n"), Ok(("", "\n")));
assert_eq!(newline("\r\n"), Ok(("", "\r\n")));
assert_eq!(newline("\n\n"), Ok(("\n", "\n")));
assert!(newline("\r").is_err());
assert!(newline("blah").is_err());
}
}