use super::{ast::*, error::ErrorMsg, pest_bridge};
#[cfg(feature = "std")]
use super::lexer::Position;
use core::result;
#[cfg(feature = "std")]
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::SimpleFiles,
term,
term::termcolor::{ColorChoice, StandardStream},
};
use displaydoc::Display;
#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use serde::Serialize;
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, Display)]
pub enum Error {
#[displaydoc("{0}")]
CDDL(String),
#[cfg_attr(
feature = "ast-span",
displaydoc("parsing error: position {position:?}, msg: {msg}")
)]
#[cfg_attr(not(feature = "ast-span"), displaydoc("parsing error: msg: {msg}"))]
PARSER {
#[cfg(feature = "ast-span")]
position: Position,
msg: ErrorMsg,
},
#[cfg(feature = "std")]
#[displaydoc("regex parsing error: {0}")]
REGEX(regex::Error),
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
pub fn cddl_from_str(input: &str, print_stderr: bool) -> std::result::Result<CDDL<'_>, String> {
pest_bridge::cddl_from_pest_str(input).map_err(|e| {
if print_stderr {
report_pest_error(&e, input);
}
e.to_string()
})
}
#[cfg(not(feature = "std"))]
#[cfg(not(target_arch = "wasm32"))]
pub fn cddl_from_str(input: &str, _print_stderr: bool) -> core::result::Result<CDDL<'_>, String> {
pest_bridge::cddl_from_pest_str(input).map_err(|e| e.to_string())
}
#[cfg(feature = "std")]
fn report_pest_error(error: &Error, input: &str) {
if let Error::PARSER {
#[cfg(feature = "ast-span")]
position,
msg,
} = error
{
let mut files = SimpleFiles::new();
let file_id = files.add("input", input);
let label_message = msg.to_string();
let label = {
#[cfg(feature = "ast-span")]
{
Label::primary(file_id, position.range.0..position.range.1).with_message(label_message)
}
#[cfg(not(feature = "ast-span"))]
{
Label::primary(file_id, 0..0).with_message(label_message)
}
};
let mut diagnostic = Diagnostic::error()
.with_message("parser errors")
.with_labels(vec![label]);
if let Some(ref extended) = msg.extended {
diagnostic = diagnostic.with_notes(vec![extended.clone()]);
}
let config = term::Config::default();
let writer = StandardStream::stderr(ColorChoice::Auto);
let _ = term::emit_to_io_write(&mut writer.lock(), &config, &files, &diagnostic);
}
}
impl CDDL<'_> {
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
pub fn from_slice(input: &[u8]) -> std::result::Result<CDDL<'_>, String> {
let str_input = std::str::from_utf8(input).map_err(|e| e.to_string())?;
pest_bridge::cddl_from_pest_str_checked(str_input).map_err(|e| {
report_pest_error(&e, str_input);
e.to_string()
})
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn cddl_from_str(input: &str) -> result::Result<JsValue, JsValue> {
#[derive(Serialize)]
struct ParserError {
position: Position,
msg: ErrorMsg,
}
match pest_bridge::cddl_from_pest_str(input) {
Ok(c) => serde_wasm_bindgen::to_value(&c).map_err(|e| JsValue::from(e.to_string())),
Err(Error::PARSER {
#[cfg(feature = "ast-span")]
position,
msg,
}) => {
let errors = vec![ParserError {
#[cfg(feature = "ast-span")]
position,
msg,
}];
Err(serde_wasm_bindgen::to_value(&errors).map_err(|e| JsValue::from(e.to_string()))?)
}
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn validate_cddl_from_str(input: &str, check_refs: bool) -> result::Result<JsValue, JsValue> {
let errors = pest_bridge::validate_cddl(input, check_refs);
serde_wasm_bindgen::to_value(&errors).map_err(|e| JsValue::from(e.to_string()))
}
#[cfg(feature = "lsp")]
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn format_cddl_from_str(input: &str) -> result::Result<String, JsValue> {
#[derive(Serialize)]
struct ParserError {
position: Position,
msg: ErrorMsg,
}
match pest_bridge::cddl_from_pest_str(input) {
Ok(c) => Ok(c.to_string()),
Err(Error::PARSER {
#[cfg(feature = "ast-span")]
position,
msg,
}) => {
let errors = vec![ParserError {
#[cfg(feature = "ast-span")]
position,
msg,
}];
Err(serde_wasm_bindgen::to_value(&errors).map_err(|e| JsValue::from(e.to_string()))?)
}
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
pub fn root_type_name_from_cddl_str(input: &str) -> std::result::Result<String, String> {
let cddl = cddl_from_str(input, false)?;
for r in cddl.rules.iter() {
if let Rule::Type { rule, .. } = r {
if rule.generic_params.is_none() {
return Ok(rule.name.to_string());
}
}
}
Err("cddl spec contains no root type".to_string())
}
#[cfg(test)]
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
#[test]
fn test_cddl_from_str_basic() {
let input = "myrule = int\n";
let result = cddl_from_str(input, false);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
}
#[test]
fn test_multiple_rules_with_reference_to_parenthesized_type() {
let input = "basic = int\nouter = (basic)\n";
let result = cddl_from_str(input, false);
assert!(result.is_ok(), "Parser errors: {:?}", result.err());
let cddl = result.unwrap();
assert_eq!(cddl.rules.len(), 2);
let rule_names: Vec<_> = cddl.rules.iter().map(|r| r.name()).collect();
assert!(rule_names.contains(&"basic".to_string()));
assert!(rule_names.contains(&"outer".to_string()));
}
}