diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
commit | 4547b622d8d29df964fa2914213088b148c498fc (patch) | |
tree | 9fc6b25f3c3add6b745be9a2400a6e96140046e9 /vendor/spdx-expression/src/parser.rs | |
parent | Releasing progress-linux version 1.66.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-4547b622d8d29df964fa2914213088b148c498fc.tar.xz rustc-4547b622d8d29df964fa2914213088b148c498fc.zip |
Merging upstream version 1.67.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/spdx-expression/src/parser.rs')
-rw-r--r-- | vendor/spdx-expression/src/parser.rs | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/vendor/spdx-expression/src/parser.rs b/vendor/spdx-expression/src/parser.rs new file mode 100644 index 000000000..cb93c5b2a --- /dev/null +++ b/vendor/spdx-expression/src/parser.rs @@ -0,0 +1,409 @@ +// SPDX-FileCopyrightText: 2022 HH Partners +// +// SPDX-License-Identifier: MIT + +//! Parsing logic for SPDX Expressions. +//! +//! The code is heavily inspired by +//! <https://github.com/Geal/nom/blob/294ffb3d9e0ade2c3b7ddfff52484b6d643dcce1/tests/arithmetic_ast.rs> +//! which is licensed under the MIT License. The source project includes the following copyright +//! statement: Copyright (c) 2014-2019 Geoffroy Couprie. + +use nom::{ + branch::alt, + bytes::complete::{tag, tag_no_case, take_while1}, + character::{ + complete::{multispace0, multispace1}, + streaming::char, + }, + combinator::{complete, map, opt, recognize}, + multi::many0, + sequence::{delimited, pair, preceded, separated_pair}, + AsChar, IResult, +}; + +use crate::expression_variant::{ExpressionVariant, SimpleExpression, WithExpression}; + +#[derive(Debug)] +enum Operator { + And, + Or, +} + +fn parentheses(i: &str) -> IResult<&str, ExpressionVariant> { + delimited( + multispace0, + delimited( + tag("("), + map(or_expression, |e| ExpressionVariant::Parens(Box::new(e))), + tag(")"), + ), + multispace0, + )(i) +} + +fn terminal_expression(i: &str) -> IResult<&str, ExpressionVariant> { + alt(( + delimited(multispace0, with_expression, multispace0), + map( + delimited(multispace0, simple_expression, multispace0), + ExpressionVariant::Simple, + ), + parentheses, + ))(i) +} + +fn with_expression(i: &str) -> IResult<&str, ExpressionVariant> { + map( + separated_pair( + simple_expression, + delimited(multispace1, tag_no_case("WITH"), multispace1), + idstring, + ), + |(lic, exc)| ExpressionVariant::With(WithExpression::new(lic, exc.to_string())), + )(i) +} + +fn fold_expressions( + initial: ExpressionVariant, + remainder: Vec<(Operator, ExpressionVariant)>, +) -> ExpressionVariant { + remainder.into_iter().fold(initial, |acc, pair| { + let (oper, expr) = pair; + match oper { + Operator::And => ExpressionVariant::And(Box::new(acc), Box::new(expr)), + Operator::Or => ExpressionVariant::Or(Box::new(acc), Box::new(expr)), + } + }) +} + +fn and_expression(i: &str) -> IResult<&str, ExpressionVariant> { + let (i, initial) = terminal_expression(i)?; + let (i, remainder) = many0(|i| { + let (i, and) = preceded(tag_no_case("AND"), terminal_expression)(i)?; + Ok((i, (Operator::And, and))) + })(i)?; + + Ok((i, fold_expressions(initial, remainder))) +} + +fn or_expression(i: &str) -> IResult<&str, ExpressionVariant> { + let (i, initial) = and_expression(i)?; + let (i, remainder) = many0(|i| { + let (i, or) = preceded(tag_no_case("OR"), and_expression)(i)?; + Ok((i, (Operator::Or, or))) + })(i)?; + + Ok((i, fold_expressions(initial, remainder))) +} + +pub fn parse_expression(i: &str) -> IResult<&str, ExpressionVariant> { + or_expression(i) +} + +fn idstring(i: &str) -> IResult<&str, &str> { + take_while1(|c: char| c.is_alphanum() || c == '-' || c == '.')(i) +} + +fn license_idstring(i: &str) -> IResult<&str, &str> { + recognize(pair(idstring, opt(complete(char('+')))))(i) +} + +fn document_ref(i: &str) -> IResult<&str, &str> { + delimited(tag("DocumentRef-"), idstring, char(':'))(i) +} + +fn license_ref(i: &str) -> IResult<&str, (Option<&str>, &str)> { + separated_pair(opt(document_ref), tag("LicenseRef-"), idstring)(i) +} + +pub fn simple_expression(i: &str) -> IResult<&str, SimpleExpression> { + alt(( + map(license_ref, |(document_ref, id)| { + let document_ref = document_ref.map(std::string::ToString::to_string); + SimpleExpression::new(id.to_string(), document_ref, true) + }), + map(license_idstring, |id| { + SimpleExpression::new(id.to_string(), None, false) + }), + ))(i) +} + +#[cfg(test)] +mod tests { + //! A lot of the test cases for parsing are copied from + //! <https://github.com/oss-review-toolkit/ort/blob/6eb18b6d36f59c6d7ec221bad1cf5d4cd6acfc8b/utils/spdx/src/test/kotlin/SpdxExpressionParserTest.kt> + //! which is licensed under the Apache License, Version 2.0 and includes the following copyright + //! statement: + //! Copyright (C) 2017-2019 HERE Europe B.V. + + use super::*; + + use pretty_assertions::assert_eq; + + #[test] + fn parse_a_license_id_correctly() { + let parsed = ExpressionVariant::parse("spdx.license-id").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Simple(SimpleExpression::new( + "spdx.license-id".to_string(), + None, + false + )) + ); + } + + #[test] + fn parse_a_license_id_starting_with_a_digit_correctly() { + let parsed = ExpressionVariant::parse("0license").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Simple(SimpleExpression::new("0license".to_string(), None, false)) + ); + } + + #[test] + fn parse_a_license_id_with_any_later_version_correctly() { + let parsed = ExpressionVariant::parse("license+").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Simple(SimpleExpression::new("license+".to_string(), None, false)) + ); + } + + #[test] + fn parse_a_document_ref_correctly() { + let parsed = ExpressionVariant::parse("DocumentRef-document:LicenseRef-license").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Simple(SimpleExpression::new( + "license".to_string(), + Some("document".to_string()), + true + )) + ); + } + + #[test] + fn parse_a_license_ref_correctly() { + let parsed = ExpressionVariant::parse("LicenseRef-license").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Simple(SimpleExpression::new("license".to_string(), None, true)) + ); + } + + #[test] + fn parse_a_with_expression_correctly() { + let parsed = ExpressionVariant::parse("license WITH exception").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::With(WithExpression::new( + SimpleExpression::new("license".to_string(), None, false), + "exception".to_string() + )) + ); + } + + #[test] + fn parse_a_complex_expression_correctly() { + let parsed = ExpressionVariant::parse( + "license1+ and ((license2 with exception1) OR license3+ AND license4 WITH exception2)", + ) + .unwrap(); + + assert_eq!( + parsed, + ExpressionVariant::And( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license1+".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::Parens(Box::new(ExpressionVariant::Or( + Box::new(ExpressionVariant::Parens(Box::new( + ExpressionVariant::With(WithExpression::new( + SimpleExpression::new("license2".to_string(), None, false), + "exception1".to_string() + )) + ))), + Box::new(ExpressionVariant::And( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license3+".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::With(WithExpression::new( + SimpleExpression::new("license4".to_string(), None, false), + "exception2".to_string() + ))) + )), + )))) + ) + ); + } + + #[test] + fn bind_plus_stronger_than_with() { + let parsed = ExpressionVariant::parse("license+ WITH exception").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::With(WithExpression::new( + SimpleExpression::new("license+".to_string(), None, false), + "exception".to_string() + )) + ); + } + + #[test] + fn bind_with_stronger_than_and() { + let parsed = ExpressionVariant::parse("license1 AND license2 WITH exception").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::And( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license1".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::With(WithExpression::new( + SimpleExpression::new("license2".to_string(), None, false), + "exception".to_string() + ))) + ) + ); + } + + #[test] + fn bind_and_stronger_than_or() { + let parsed = ExpressionVariant::parse("license1 OR license2 AND license3").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Or( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license1".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::And( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license2".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license3".to_string(), + None, + false + ))) + )) + ) + ); + } + + #[test] + fn bind_the_and_operator_left_associative() { + let parsed = ExpressionVariant::parse("license1 AND license2 AND license3").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::And( + Box::new(ExpressionVariant::And( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license1".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license2".to_string(), + None, + false + ))) + )), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license3".to_string(), + None, + false + ))), + ) + ); + } + + #[test] + fn bind_the_or_operator_left_associative() { + let parsed = ExpressionVariant::parse("license1 OR license2 OR license3").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::Or( + Box::new(ExpressionVariant::Or( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license1".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license2".to_string(), + None, + false + ))) + )), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license3".to_string(), + None, + false + ))), + ) + ); + } + + #[test] + fn respect_parentheses_for_binding_strength_of_operators() { + let parsed = ExpressionVariant::parse("(license1 OR license2) AND license3").unwrap(); + assert_eq!( + parsed, + ExpressionVariant::And( + Box::new(ExpressionVariant::Parens(Box::new(ExpressionVariant::Or( + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license1".to_string(), + None, + false + ))), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license2".to_string(), + None, + false + ))) + )))), + Box::new(ExpressionVariant::Simple(SimpleExpression::new( + "license3".to_string(), + None, + false + ))), + ) + ); + } + + #[test] + fn fail_if_plus_is_used_in_an_exception_expression() { + let parsed = ExpressionVariant::parse("license WITH exception+"); + assert!(parsed.is_err()); + } + + #[test] + fn fail_if_a_compound_expressions_is_used_before_with() { + let parsed = ExpressionVariant::parse("(license1 AND license2) WITH exception"); + assert!(parsed.is_err()); + } + + #[test] + fn fail_on_an_invalid_symbol() { + let parsed = ExpressionVariant::parse("/"); + assert!(parsed.is_err()); + } + + #[test] + fn fail_on_a_syntax_error() { + let parsed = ExpressionVariant::parse("(("); + assert!(parsed.is_err()); + } +} |