Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions src/expression/parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
LicenseItem, LicenseReq, ParseMode,
AdditionItem, LicenseItem, LicenseReq, ParseMode,
error::{ParseError, Reason},
expression::{ExprNode, Expression, ExpressionReq, Operator},
lexer::{Lexer, Token},
Expand Down Expand Up @@ -114,6 +114,16 @@ impl Expression {
can.push_str("LicenseRef-");
can.push_str(lic_ref);
}
Token::AdditionRef { doc_ref, add_ref } => {
if let Some(dr) = doc_ref {
can.push_str("DocumentRef-");
can.push_str(dr);
can.push(':');
}

can.push_str("AdditionRef-");
can.push_str(add_ref);
}
}
}

Expand Down Expand Up @@ -177,10 +187,10 @@ impl Expression {
let expected: &[&str] = match last_token {
None | Some(Token::And | Token::Or | Token::OpenParen) => &["<license>", "("],
Some(Token::CloseParen) => &["AND", "OR"],
Some(Token::Exception(_)) => &["AND", "OR", ")"],
Some(Token::Exception(_) | Token::AdditionRef { .. }) => &["AND", "OR", ")"],
Some(Token::Spdx(_)) => &["AND", "OR", "WITH", ")", "+"],
Some(Token::LicenseRef { .. } | Token::Plus) => &["AND", "OR", "WITH", ")"],
Some(Token::With) => &["<exception>"],
Some(Token::With) => &["<addition>"],
};

Err(ParseError {
Expand Down Expand Up @@ -219,7 +229,7 @@ impl Expression {
doc_ref: doc_ref.map(String::from),
lic_ref: String::from(*lic_ref),
},
exception: None,
addition: None,
},
span: lt.span.start as u32..lt.span.end as u32,
}));
Expand Down Expand Up @@ -281,6 +291,7 @@ impl Expression {
| Token::LicenseRef { .. }
| Token::CloseParen
| Token::Exception(_)
| Token::AdditionRef { .. }
| Token::Plus,
) => {
let new_op = match lt.token {
Expand Down Expand Up @@ -330,6 +341,7 @@ impl Expression {
| Token::LicenseRef { .. }
| Token::Plus
| Token::Exception(_)
| Token::AdditionRef { .. }
| Token::CloseParen,
) => {
while let Some(top) = op_stack.pop() {
Expand Down Expand Up @@ -357,7 +369,19 @@ impl Expression {
Token::Exception(exc) => match last_token {
Some(Token::With) => match expr_queue.last_mut() {
Some(ExprNode::Req(lic)) => {
lic.req.exception = Some(*exc);
lic.req.addition = Some(AdditionItem::Spdx(*exc));
}
_ => unreachable!(),
},
_ => return make_err_for_token(last_token, lt.span),
},
Token::AdditionRef { doc_ref, add_ref } => match last_token {
Some(Token::With) => match expr_queue.last_mut() {
Some(ExprNode::Req(lic)) => {
lic.req.addition = Some(AdditionItem::Other {
doc_ref: doc_ref.map(String::from),
add_ref: String::from(*add_ref),
});
}
_ => unreachable!(),
},
Expand All @@ -374,6 +398,7 @@ impl Expression {
Token::Spdx(_)
| Token::LicenseRef { .. }
| Token::Exception(_)
| Token::AdditionRef { .. }
| Token::CloseParen
| Token::Plus,
) => {}
Expand Down
47 changes: 46 additions & 1 deletion src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ pub enum Token<'a> {
},
/// A recognized SPDX exception id
Exception(ExceptionId),
/// A `AdditionRef-` prefixed id, with an optional `DocumentRef-`
AdditionRef {
doc_ref: Option<&'a str>,
add_ref: &'a str,
},
/// A postfix `+` indicating "or later" for a particular SPDX license id
Plus,
/// A `(` for starting a group
Expand Down Expand Up @@ -111,6 +116,13 @@ impl Token<'_> {
}) + "LicenseRef-".len()
+ lic_ref.len()
}
Token::AdditionRef { doc_ref, add_ref } => {
doc_ref.map_or(0, |d| {
// +1 is for the `:`
"DocumentRef-".len() + d.len() + 1
}) + "AdditionRef-".len()
+ add_ref.len()
}
}
}
}
Expand Down Expand Up @@ -175,6 +187,12 @@ impl<'a> Lexer<'a> {
})
}

/// Return a document ref if found - equivalent to the regex `^DocumentRef-([-a-zA-Z0-9.]+)`
#[inline]
fn find_document_ref(text: &'a str) -> Option<&'a str> {
Self::find_ref("DocumentRef-", text)
}

/// Return a license ref if found - equivalent to the regex `^LicenseRef-([-a-zA-Z0-9.]+)`
#[inline]
fn find_license_ref(text: &'a str) -> Option<&'a str> {
Expand All @@ -185,10 +203,25 @@ impl<'a> Lexer<'a> {
/// equivalent to the regex `^DocumentRef-([-a-zA-Z0-9.]+):LicenseRef-([-a-zA-Z0-9.]+)`
fn find_document_and_license_ref(text: &'a str) -> Option<(&'a str, &'a str)> {
let split = text.split_once(':');
let doc_ref = split.and_then(|(doc, _)| Self::find_ref("DocumentRef-", doc));
let doc_ref = split.and_then(|(doc, _)| Self::find_document_ref(doc));
let lic_ref = split.and_then(|(_, lic)| Self::find_license_ref(lic));
Option::zip(doc_ref, lic_ref)
}

/// Return an addition ref if found - equivalent to the regex `^AdditionRef-([-a-zA-Z0-9.]+)`
#[inline]
fn find_addition_ref(text: &'a str) -> Option<&'a str> {
Self::find_ref("AdditionRef-", text)
}

/// Return a document ref and license ref if found,
/// equivalent to the regex `^DocumentRef-([-a-zA-Z0-9.]+):AdditionRef-([-a-zA-Z0-9.]+)`
fn find_document_and_addition_ref(text: &'a str) -> Option<(&'a str, &'a str)> {
let split = text.split_once(':');
let doc_ref = split.and_then(|(doc, _)| Self::find_document_ref(doc));
let lic_ref = split.and_then(|(_, add)| Self::find_addition_ref(add));
Option::zip(doc_ref, lic_ref)
}
}

/// A wrapper around a particular token that includes the span of the characters
Expand Down Expand Up @@ -265,6 +298,18 @@ impl<'a> Iterator for Lexer<'a> {
doc_ref: None,
lic_ref,
})
} else if let Some((doc_ref, add_ref)) =
Lexer::find_document_and_addition_ref(m)
{
ok_token(Token::AdditionRef {
doc_ref: Some(doc_ref),
add_ref,
})
} else if let Some(add_ref) = Lexer::find_addition_ref(m) {
ok_token(Token::AdditionRef {
doc_ref: None,
add_ref,
})
} else if let Some((lic_id, token_len)) =
if self.mode.allow_imprecise_license_names {
crate::imprecise_license_id(self.inner)
Expand Down
114 changes: 109 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,9 @@ impl fmt::Debug for ExceptionId {
pub struct LicenseReq {
/// The license
pub license: LicenseItem,
/// The exception allowed for this license, as specified following
/// The additional text for this license, as specified following
/// the `WITH` operator
pub exception: Option<ExceptionId>,
pub addition: Option<AdditionItem>,
}

impl From<LicenseId> for LicenseReq {
Expand All @@ -251,7 +251,7 @@ impl From<LicenseId> for LicenseReq {
id,
or_later: false,
},
exception: None,
addition: None,
}
}
}
Expand All @@ -260,8 +260,8 @@ impl fmt::Display for LicenseReq {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.license.fmt(f)?;

if let Some(ref exe) = self.exception {
write!(f, " WITH {}", exe.name)?;
if let Some(ref exe) = self.addition {
write!(f, " WITH {exe}")?;
}

Ok(())
Expand Down Expand Up @@ -398,6 +398,110 @@ impl fmt::Display for LicenseItem {
}
}

/// A single addition term in a addition expression, according to the SPDX spec.
///
/// This can be either an SPDX license exception, which is mapped to a [`ExceptionId`]
/// from a valid SPDX short identifier, or else a document and/or addition ref
#[derive(Debug, Clone, Eq)]
pub enum AdditionItem {
/// A regular SPDX license exception id
Spdx(ExceptionId),
Other {
/// Purpose: Identify any external SPDX documents referenced within this SPDX document.
/// See the [spec](https://spdx.org/spdx-specification-21-web-version#h.h430e9ypa0j9) for
/// more details.
doc_ref: Option<String>,
/// Purpose: Provide a locally unique identifier to refer to additional text that are not found on the SPDX License List.
/// See the [spec](https://spdx.org/spdx-specification-21-web-version#h.4f1mdlm) for
/// more details.
add_ref: String,
},
}

impl AdditionItem {
/// Returns the license exception identifier, if it is a recognized SPDX license exception
/// and not a license exception referencer
#[must_use]
pub fn id(&self) -> Option<ExceptionId> {
match self {
Self::Spdx(id) => Some(*id),
Self::Other { .. } => None,
}
}
}

impl Ord for AdditionItem {
fn cmp(&self, o: &Self) -> Ordering {
match (self, o) {
(Self::Spdx(a), Self::Spdx(b)) => match a.cmp(b) {
Ordering::Equal => a.cmp(b),
o => o,
},
(
Self::Other {
doc_ref: ad,
add_ref: aa,
},
Self::Other {
doc_ref: bd,
add_ref: ba,
},
) => match ad.cmp(bd) {
Ordering::Equal => aa.cmp(ba),
o => o,
},
(Self::Spdx(_), Self::Other { .. }) => Ordering::Less,
(Self::Other { .. }, Self::Spdx(_)) => Ordering::Greater,
}
}
}

impl PartialOrd for AdditionItem {
#[allow(clippy::non_canonical_partial_ord_impl)]
fn partial_cmp(&self, o: &Self) -> Option<Ordering> {
match (self, o) {
(Self::Spdx(a), Self::Spdx(b)) => a.partial_cmp(b),
(
Self::Other {
doc_ref: ad,
add_ref: aa,
},
Self::Other {
doc_ref: bd,
add_ref: ba,
},
) => match ad.cmp(bd) {
Ordering::Equal => aa.partial_cmp(ba),
o => Some(o),
},
(Self::Spdx(_), Self::Other { .. }) => Some(cmp::Ordering::Less),
(Self::Other { .. }, Self::Spdx(_)) => Some(cmp::Ordering::Greater),
}
}
}

impl PartialEq for AdditionItem {
fn eq(&self, o: &Self) -> bool {
matches!(self.partial_cmp(o), Some(cmp::Ordering::Equal))
}
}

impl fmt::Display for AdditionItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
AdditionItem::Spdx(id) => id.name.fmt(f),
AdditionItem::Other {
doc_ref: Some(d),
add_ref: a,
} => write!(f, "DocumentRef-{d}:AdditionRef-{a}"),
AdditionItem::Other {
doc_ref: None,
add_ref: a,
} => write!(f, "AdditionRef-{a}"),
}
}
}

/// Attempts to find a [`LicenseId`] for the string.
///
/// Note that any `+` at the end is trimmed when searching for a match.
Expand Down
Loading