Skip to content

Commit

Permalink
Merge pull request #121 from anweiss/120-group-type-choice-parsing-va…
Browse files Browse the repository at this point in the history
…lidation-errors

Fix group and type choice parsing and validation errors
  • Loading branch information
anweiss authored Sep 12, 2022
2 parents 90d5fa1 + e34b347 commit 45442c5
Show file tree
Hide file tree
Showing 13 changed files with 777 additions and 872 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
minimum-version-check:
strategy:
matrix:
rust_toolchain: [1.56.0]
rust_toolchain: [1.56.1]
os: [ubuntu-latest, macOS-latest, windows-latest]
name: minimum version check using Rust ${{ matrix.rust_toolchain }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A Rust implementation of the Concise data definition language (CDDL). CDDL is an

This crate includes a handwritten parser and lexer for CDDL, and its development has been heavily inspired by the techniques outlined in Thorsten Ball's book ["Writing An Interpretor In Go"](https://interpreterbook.com/). The AST has been built to closely match the rules defined by the ABNF grammar in [Appendix B.](https://tools.ietf.org/html/rfc8610#appendix-B) of the spec. All CDDL must use UTF-8 for its encoding per the spec.

This crate supports validation of both CBOR and JSON data structures. The minimum supported Rust version (MSRV) is 1.56.0.
This crate supports validation of both CBOR and JSON data structures. The minimum supported Rust version (MSRV) is 1.56.1.

Also bundled into this repository is a basic language server implementation and extension for Visual Studio Code for editing CDDL. The implementation is backed by the compiled WebAssembly target included in this crate.

Expand Down Expand Up @@ -36,7 +36,7 @@ Rust is a systems programming language designed around safety and is ideally-sui

## CLI

A CLI is available for various platforms. The tool supports parsing of `.cddl` files for verifying conformance against RFC 8610. It can also be used to validate `.json` documents and `.cbor` binary files against `.cddl` documents. Detailed information about the JSON and CBOR validation implementation can be found in the sections below.
A CLI is available for various platforms. The tool supports parsing of CDDL files for verifying conformance against RFC 8610. It can also be used to validate JSON documents and CBOR binary files against CDDL documents. Detailed information about the JSON and CBOR validation implementation can be found in the sections below.

### Installation

Expand Down Expand Up @@ -66,7 +66,7 @@ cddl help

If using Docker:

> Replace `<version>` with an appropriate [release](https://github.com/anweiss/cddl/releases) tag. Requires use of the `--volume` argument for mounting `.cddl` documents into the container when executing the command. `.json` or `.cbor` files can either be included in the volume mount or passed into the command via STDIN.
> Replace `<version>` with an appropriate [release](https://github.com/anweiss/cddl/releases) tag. Requires use of the `--volume` argument for mounting CDDL documents into the container when executing the command. JSON or CBOR files can either be included in the volume mount or passed into the command via STDIN.
```sh
docker run -it --rm -v $PWD:/cddl -w /cddl ghcr.io/anweiss/cddl-cli:<version> help
Expand Down
10 changes: 10 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,16 @@ pub struct Group<'a> {
pub span: Span,
}

impl<'a> From<GroupEntry<'a>> for Group<'a> {
fn from(ge: GroupEntry<'a>) -> Self {
Group {
group_choices: vec![GroupChoice::new(vec![ge])],
#[cfg(feature = "ast-span")]
span: Span::default(),
}
}
}

impl<'a> fmt::Display for Group<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut group_str = String::new();
Expand Down
71 changes: 34 additions & 37 deletions src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,39 +1083,39 @@ mod tests {
);

let expected_tok = [
(COMMENT(" this is a comment".into()), "; this is a comment"),
(COMMENT(" this is a comment"), "; this is a comment"),
(
COMMENT(" this is another comment".into()),
COMMENT(" this is another comment"),
"; this is another comment",
),
(NEWLINE, ""),
(IDENT("mynumber".into(), None), "mynumber"),
(IDENT("mynumber", None), "mynumber"),
(ASSIGN, "="),
(VALUE(Value::FLOAT(10.5)), "10.5"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mytag".into(), None), "mytag"),
(IDENT("mytag", None), "mytag"),
(ASSIGN, "="),
(TAG(Some(6), Some(1234)), "#6.1234"),
(LPAREN, "("),
(TSTR, "tstr"),
(RPAREN, ")"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("myfirstrule".into(), None), "myfirstrule"),
(IDENT("myfirstrule", None), "myfirstrule"),
(ASSIGN, "="),
(VALUE(Value::TEXT("myotherrule".into())), "\"myotherrule\""),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mybytestring".into(), None), "mybytestring"),
(IDENT("mybytestring", None), "mybytestring"),
(ASSIGN, "="),
(
VALUE(Value::BYTE(ByteValue::UTF8(b"hello there".as_ref().into()))),
"'hello there'",
),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mybase16rule".into(), None), "mybase16rule"),
(IDENT("mybase16rule", None), "mybase16rule"),
(ASSIGN, "="),
(
VALUE(Value::BYTE(ByteValue::B16(
Expand All @@ -1125,7 +1125,7 @@ mod tests {
),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mybase64rule".into(), None), "mybase64rule"),
(IDENT("mybase64rule", None), "mybase64rule"),
(ASSIGN, "="),
(
VALUE(Value::BYTE(ByteValue::B64(
Expand All @@ -1135,66 +1135,66 @@ mod tests {
),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mysecondrule".into(), None), "mysecondrule"),
(IDENT("mysecondrule", None), "mysecondrule"),
(ASSIGN, "="),
(IDENT("mynumber".into(), None), "mynumber"),
(IDENT("mynumber", None), "mynumber"),
(RANGEOP(true), ".."),
(VALUE(Value::FLOAT(100.5)), "100.5"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("myintrule".into(), None), "myintrule"),
(IDENT("myintrule", None), "myintrule"),
(ASSIGN, "="),
(VALUE(Value::INT(-10)), "-10"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mysignedfloat".into(), None), "mysignedfloat"),
(IDENT("mysignedfloat", None), "mysignedfloat"),
(ASSIGN, "="),
(VALUE(Value::FLOAT(-10.5)), "-10.5"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("myintrange".into(), None), "myintrange"),
(IDENT("myintrange", None), "myintrange"),
(ASSIGN, "="),
(VALUE(Value::INT(-10)), "-10"),
(RANGEOP(true), ".."),
(VALUE(Value::UINT(10)), "10"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("mycontrol".into(), None), "mycontrol"),
(IDENT("mycontrol", None), "mycontrol"),
(ASSIGN, "="),
(IDENT("mynumber".into(), None), "mynumber"),
(IDENT("mynumber", None), "mynumber"),
(GT, ".gt"),
(VALUE(Value::UINT(0)), "0"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("@terminal-color".into(), None), "@terminal-color"),
(IDENT("@terminal-color", None), "@terminal-color"),
(ASSIGN, "="),
(IDENT("basecolors".into(), None), "basecolors"),
(IDENT("basecolors", None), "basecolors"),
(TCHOICE, "/"),
(IDENT("othercolors".into(), None), "othercolors"),
(COMMENT(" an inline comment".into()), "; an inline comment"),
(IDENT("othercolors", None), "othercolors"),
(COMMENT(" an inline comment"), "; an inline comment"),
(NEWLINE, ""),
(IDENT("messages".into(), None), "messages"),
(IDENT("messages", None), "messages"),
(ASSIGN, "="),
(IDENT("message".into(), None), "message"),
(IDENT("message", None), "message"),
(LANGLEBRACKET, "<"),
(VALUE(Value::TEXT("reboot".into())), "\"reboot\""),
(COMMA, ","),
(VALUE(Value::TEXT("now".into())), "\"now\""),
(RANGLEBRACKET, ">"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("address".into(), None), "address"),
(IDENT("address", None), "address"),
(ASSIGN, "="),
(LBRACE, "{"),
(IDENT("delivery".into(), None), "delivery"),
(IDENT("delivery", None), "delivery"),
(RBRACE, "}"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("delivery".into(), None), "delivery"),
(IDENT("delivery", None), "delivery"),
(ASSIGN, "="),
(LPAREN, "("),
(NEWLINE, ""),
(IDENT("street".into(), None), "street"),
(IDENT("street", None), "street"),
(COLON, ":"),
(TSTR, "tstr"),
(COMMA, ","),
Expand All @@ -1204,46 +1204,43 @@ mod tests {
(ARROWMAP, "=>"),
(UINT, "uint"),
(COMMA, ","),
(IDENT("city".into(), None), "city"),
(IDENT("city", None), "city"),
(GCHOICE, "//"),
(NEWLINE, ""),
(IDENT("po-box".into(), None), "po-box"),
(IDENT("po-box", None), "po-box"),
(COLON, ":"),
(UINT, "uint"),
(COMMA, ","),
(IDENT("city".into(), None), "city"),
(IDENT("city", None), "city"),
(GCHOICE, "//"),
(NEWLINE, ""),
(IDENT("per-pickup".into(), None), "per-pickup"),
(IDENT("per-pickup", None), "per-pickup"),
(COLON, ":"),
(TRUE, "true"),
(NEWLINE, ""),
(RPAREN, ")"),
(NEWLINE, ""),
(NEWLINE, ""),
(IDENT("city".into(), None), "city"),
(IDENT("city", None), "city"),
(ASSIGN, "="),
(LPAREN, "("),
(NEWLINE, ""),
(IDENT("name".into(), None), "name"),
(IDENT("name", None), "name"),
(COLON, ":"),
(TSTR, "tstr"),
(NEWLINE, ""),
(IDENT("zip-code".into(), None), "zip-code"),
(IDENT("zip-code", None), "zip-code"),
(COLON, ":"),
(UINT, "uint"),
(NEWLINE, ""),
(VALUE(Value::UINT(1)), "1"),
(ASTERISK, "*"),
(VALUE(Value::UINT(3)), "3"),
(
IDENT("tcp-option".into(), Some(SocketPlug::GROUP)),
"$$tcp-option",
),
(IDENT("tcp-option", Some(SocketPlug::GROUP)), "$$tcp-option"),
(COMMA, ","),
(NEWLINE, ""),
(RPAREN, ")"),
(COMMENT(" test".into()), "; test"),
(COMMENT(" test"), "; test"),
];

let mut l = Lexer::new(input);
Expand Down
16 changes: 8 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
//!
//! This crate supports validation of both CBOR and JSON data structures. An
//! extremely basic REPL is included as well. This crate's minimum supported
//! Rust version (MSRV) is 1.56.0.
//! Rust version (MSRV) is 1.56.1.
//!
//! Also bundled into this repository is a basic language server implementation
//! and extension for Visual Studio Code for editing CDDL. The implementation is
Expand Down Expand Up @@ -66,11 +66,11 @@
//!
//! ## CLI
//!
//! A CLI is available for various platforms. The tool supports parsing of
//! `.cddl` files for verifying conformance against RFC 8610. It can also be
//! used to validate `.json` documents and `.cbor` binary files against `.cddl`
//! documents. Detailed information about the JSON and CBOR validation
//! implementation can be found in the sections below.
//! A CLI is available for various platforms. The tool supports parsing of CDDL
//! files for verifying conformance against RFC 8610. It can also be used to
//! validate JSON documents and CBOR binary files against CDDL documents.
//! Detailed information about the JSON and CBOR validation implementation can
//! be found in the sections below.
//!
//! ### Installation
//!
Expand Down Expand Up @@ -105,8 +105,8 @@
//! > Replace `<version>` with an appropriate
//! > [release](https://github.com/anweiss/cddl/releases) tag. Requires use of
//! > the `--volume` argument for mounting `.cddl` documents into the container
//! > when executing the command. `.json` or `.cbor` files can either be
//! > included in the volume mount or passed into the command via STDIN.
//! > when executing the command. JSON or CBOR files can either be included in
//! > the volume mount or passed into the command via STDIN.
//!
//! ```sh
//! docker run -it --rm -v $PWD:/cddl -w /cddl ghcr.io/anweiss/cddl-cli:<version> help
Expand Down
41 changes: 41 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ impl<'a> Parser<'a> {
Ok(r) => {
let rule_exists =
|existing_rule: &Rule| r.name() == existing_rule.name() && !r.is_choice_alternate();

if c.rules.iter().any(rule_exists) {
#[cfg(feature = "ast-span")]
{
Expand Down Expand Up @@ -558,6 +559,45 @@ impl<'a> Parser<'a> {
#[cfg(not(feature = "ast-comments"))]
self.advance_newline()?;

// If token is group socket or rule is a group plug alternative, parse
// as group rule
if matches!(self.cur_token, Token::IDENT(_, Some(SocketPlug::GROUP)))
|| is_group_choice_alternate
{
let ge = self.parse_grpent(true)?;

#[cfg(feature = "ast-comments")]
let comments_after_rule = self.collect_comments()?;
#[cfg(not(feature = "ast-comments"))]
self.advance_newline()?;

#[cfg(feature = "ast-span")]
let span = (
begin_rule_range,
self.parser_position.range.1,
begin_rule_line,
);

self.current_rule_generic_param_idents = None;

return Ok(Rule::Group {
rule: Box::from(GroupRule {
name: ident,
generic_params: gp,
is_group_choice_alternate,
entry: ge,
#[cfg(feature = "ast-comments")]
comments_before_assigng: comments_before_assign,
#[cfg(feature = "ast-comments")]
comments_after_assigng: comments_after_assign,
}),
#[cfg(feature = "ast-comments")]
comments_after_rule,
#[cfg(feature = "ast-span")]
span,
});
}

match self.cur_token {
// Check for an occurrence indicator of uint followed by an asterisk '*'
Token::VALUE(token::Value::UINT(_)) => {
Expand Down Expand Up @@ -744,6 +784,7 @@ impl<'a> Parser<'a> {
_ => {
// If type rule is an unwrap type, advance token after parsing type
let advance_token = matches!(self.cur_token, Token::UNWRAP);

#[cfg(feature = "ast-comments")]
let mut t = self.parse_type(None)?;
#[cfg(not(feature = "ast-comments"))]
Expand Down
Loading

0 comments on commit 45442c5

Please sign in to comment.