mirror of https://github.com/kdl-org/kdl-rs.git
some progress + tests
This commit is contained in:
parent
100a736145
commit
a316f6d2ef
|
|
@ -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>,
|
||||
|
|
|
|||
12
src/node.rs
12
src/node.rs
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
161
src/parser.rs
161
src/parser.rs
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue