use peg::parser;
use std::fmt::Display;
#[derive(Clone, PartialEq, Debug)]
pub struct Message {
pub message: String,
pub kind: MessageKind,
pub location: Location,
}
impl Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = format!(
"{} at {}:{}",
self.message, self.location.start, self.location.end
);
write!(f, "{str}")
}
}
impl AstNode {
pub fn location(&self) -> Location {
match self {
AstNode::Str { location, .. } => location.to_owned(),
AstNode::Boolean { location, .. } => location.to_owned(),
AstNode::Number { location, .. } => location.to_owned(),
AstNode::Null { location, .. } => location.to_owned(),
AstNode::DateTime { location, .. } => location.to_owned(),
AstNode::LocalDateTime { location, .. } => location.to_owned(),
AstNode::Period { location, .. } => location.to_owned(),
AstNode::Time { location, .. } => location.to_owned(),
AstNode::LocalTime { location, .. } => location.to_owned(),
AstNode::Date { location, .. } => location.to_owned(),
AstNode::NameIdentifier { location, .. } => location.to_owned(),
AstNode::VariableReference { location, .. } => location.to_owned(),
AstNode::Array { location, .. } => location.to_owned(),
AstNode::Comparison { location, .. } => location.to_owned(),
AstNode::Logical { location, .. } => location.to_owned(),
AstNode::Not { location, .. } => location.to_owned(),
AstNode::ValueSelector { location, .. } => location.to_owned(),
AstNode::InfixFunctionCall { location, .. } => location.to_owned(),
AstNode::FunctionCall { location, .. } => location.to_owned(),
AstNode::IfElse { location, .. } => location.to_owned(),
AstNode::Default { location, .. } => location.to_owned(),
AstNode::Document { location, .. } => location.to_owned(),
AstNode::VersionDirective { location, .. } => location.to_owned(),
AstNode::InputDirective { location, .. } => location.to_owned(),
AstNode::OutputDirective { location, .. } => location.to_owned(),
AstNode::Object { location, .. } => location.to_owned(),
AstNode::KeyValuePair { location, .. } => location.to_owned(),
AstNode::Option { location, .. } => location.to_owned(),
AstNode::ContentType { location, .. } => location.to_owned(),
AstNode::ImportDirective { location, .. } => location.to_owned(),
AstNode::ConditionalKeyValuePair { location, .. } => location.to_owned(),
AstNode::StrInterpolation { location, .. } => location.to_owned(),
AstNode::NullSafeSelector { location, .. } => location.to_owned(),
AstNode::AttributeSelector { location, .. } => location.to_owned(),
AstNode::MultiValueSelector { location, .. } => location.to_owned(),
AstNode::Uri { location, .. } => location.to_owned(),
AstNode::NamespaceDirective { location, .. } => location.to_owned(),
AstNode::Name { location, .. } => location.to_owned(),
AstNode::MetadataSelector { location, .. } => location.to_owned(),
}
}
pub fn name(&self) -> &'static str {
match self {
AstNode::Str { .. } => "Str",
AstNode::Boolean { .. } => "Boolean",
AstNode::Number { .. } => "Number",
AstNode::Null { .. } => "Null",
AstNode::DateTime { .. } => "DateTime",
AstNode::LocalDateTime { .. } => "LocalDateTime",
AstNode::Period { .. } => "Period",
AstNode::Time { .. } => "Time",
AstNode::LocalTime { .. } => "LocalTime",
AstNode::Date { .. } => "Date",
AstNode::NameIdentifier { .. } => "NameIdentifier",
AstNode::VariableReference { .. } => "VariableReference",
AstNode::Array { .. } => "Array",
AstNode::Comparison { .. } => "Comparison",
AstNode::Logical { .. } => "Logical",
AstNode::Not { .. } => "Not",
AstNode::ValueSelector { .. } => "ValueSelector",
AstNode::NullSafeSelector { .. } => "NullSafeSelector",
AstNode::InfixFunctionCall { .. } => "InfixFunctionCall",
AstNode::FunctionCall { .. } => "FunctionCall",
AstNode::IfElse { .. } => "IfElse",
AstNode::Default { .. } => "Default",
AstNode::Document { .. } => "Document",
AstNode::VersionDirective { .. } => "VersionDirective",
AstNode::InputDirective { .. } => "InputDirective",
AstNode::OutputDirective { .. } => "OutputDirective",
AstNode::Object { .. } => "Object",
AstNode::KeyValuePair { .. } => "KeyValuePair",
AstNode::Option { .. } => "Option",
AstNode::ContentType { .. } => "ContentType",
AstNode::ImportDirective { .. } => "ImportDirective",
AstNode::ConditionalKeyValuePair { .. } => "ConditionalKeyValuePair",
AstNode::StrInterpolation { .. } => "StrInterpolation",
AstNode::AttributeSelector { .. } => "AttributeSelector",
AstNode::MultiValueSelector { .. } => "MultiValueSelector",
AstNode::NamespaceDirective { .. } => "NamespaceDirective",
AstNode::Uri { .. } => "Uri",
AstNode::Name { .. } => "Name",
AstNode::MetadataSelector { .. } => "MetadataSelector",
}
}
pub fn get_value(&self) -> Option<String> {
match self {
AstNode::Str { value, .. } => Some(value.to_owned()),
AstNode::Uri { value, .. } => Some(value.to_owned()),
AstNode::Boolean { value, .. } => Some(value.to_string()),
AstNode::Number { value, .. } => Some(value.to_string()),
AstNode::Null { .. } => Some("null".to_string()),
AstNode::DateTime { value, .. } => Some(value.to_string()),
AstNode::LocalDateTime { value, .. } => Some(value.to_owned()),
AstNode::Period { value, .. } => Some(value.to_owned()),
AstNode::Time { value, .. } => Some(value.to_owned()),
AstNode::LocalTime { value, .. } => Some(value.to_owned()),
AstNode::Date { value, .. } => Some(value.to_owned()),
AstNode::NameIdentifier { name, .. } => Some(name.join("::").to_owned()),
_ => None,
}
}
pub fn children(&self) -> Vec<AstNode> {
match self {
AstNode::VariableReference { reference, .. } => {
vec![reference.as_ref().to_owned()]
}
AstNode::Array { items, .. } => items.to_vec(),
AstNode::StrInterpolation { segments, .. } => segments.to_vec(),
AstNode::Comparison { lhs, rhs, .. } => {
vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
}
AstNode::Logical { lhs, rhs, .. } => {
vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
}
AstNode::Not { rhs, .. } => {
vec![rhs.as_ref().to_owned()]
}
AstNode::ValueSelector { lhs, rhs, .. } => {
vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
}
AstNode::MultiValueSelector { lhs, rhs, .. } => {
vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
}
AstNode::MetadataSelector { lhs, rhs, .. } => {
let mut children = vec![lhs.as_ref().to_owned()];
let x = rhs.as_ref().to_owned();
if x.is_some() {
if let Some(ast_node) = x {
children.push(ast_node.to_owned());
}
}
children
}
AstNode::AttributeSelector { lhs, rhs, .. } => {
vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
}
AstNode::InfixFunctionCall { lhs, rhs, func, .. } => {
vec![
lhs.as_ref().to_owned(),
rhs.as_ref().to_owned(),
func.as_ref().to_owned(),
]
}
AstNode::FunctionCall { lhs, args, .. } => {
let mut vec1 = vec![lhs.as_ref().to_owned()];
vec1.append(&mut args.to_vec());
vec1
}
AstNode::IfElse {
if_branch,
condition,
else_branch,
..
} => {
vec![
if_branch.as_ref().to_owned(),
condition.as_ref().to_owned(),
else_branch.as_ref().to_owned(),
]
}
AstNode::Default { lhs, rhs, .. } => {
vec![lhs.as_ref().to_owned(), rhs.as_ref().to_owned()]
}
AstNode::Document { body, header, .. } => {
let mut result = vec![body.as_ref().to_owned()];
match header {
None => {}
Some(some_header) => {
result.append(&mut some_header.to_vec());
}
}
result
}
AstNode::InputDirective {
name,
data_format,
options,
..
} => {
let mut result = vec![name.as_ref().to_owned(), data_format.as_ref().to_owned()];
result.append(&mut options.to_vec());
result
}
AstNode::OutputDirective {
data_format,
options,
..
} => {
let mut result = vec![data_format.as_ref().to_owned()];
result.append(&mut options.to_vec());
result
}
AstNode::Object { fields, .. } => fields.to_vec(),
AstNode::KeyValuePair { key, value, .. } => {
vec![key.as_ref().to_owned(), value.as_ref().to_owned()]
}
AstNode::Option { name, value, .. } => {
vec![name.as_ref().to_owned(), value.as_ref().to_owned()]
}
AstNode::ImportDirective { module, .. } => {
let result = vec![module.name.as_ref().to_owned()];
result
}
AstNode::NullSafeSelector { lhs, .. } => {
let result = vec![lhs.as_ref().to_owned()];
result
}
AstNode::ConditionalKeyValuePair {
key,
value,
condition,
..
} => {
vec![
key.as_ref().to_owned(),
value.as_ref().to_owned(),
condition.as_ref().to_owned(),
]
}
AstNode::NamespaceDirective { prefix, uri, .. } => {
vec![prefix.as_ref().to_owned(), uri.as_ref().to_owned()]
}
AstNode::Name { prefix, value, .. } => {
let mut result = vec![];
if prefix.is_some() {
result.push(prefix.to_owned().unwrap().to_owned());
}
result.push(value.as_ref().to_owned());
result
}
_ => {
vec![]
}
}
}
}
impl Display for AstNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn to_ast_string(node: &AstNode, indent: usize) -> String {
let mut result = String::new();
let indentation_str = " ".repeat(indent);
result.push_str(indentation_str.as_str());
result.push_str("|-");
result.push_str(node.name());
result.push(' ');
result.push_str(node.location().to_string().as_str());
if node.get_value().is_some() {
result.push_str(" Value=");
result.push('\'');
result.push_str(node.get_value().unwrap().as_str());
result.push('\'');
}
result.push('\n');
for child in node.children() {
result.push_str(to_ast_string(&child, indent + 1).as_str())
}
result
}
write!(f, "{}", to_ast_string(self, 0))
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct DWParseResult {
pub ast: AstNode,
pub messages: Vec<Message>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct DWError {
pub messages: Vec<Message>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct Location {
pub start: usize,
pub end: usize,
}
impl Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Start='{}' End='{}'", self.start, self.end)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum AstNode {
Str {
value: String,
quote: Option<char>,
location: Location,
},
Boolean {
value: bool,
location: Location,
},
Number {
value: String,
location: Location,
},
Null {
location: Location,
},
DateTime {
value: String,
location: Location,
},
LocalDateTime {
value: String,
location: Location,
},
Period {
value: String,
location: Location,
},
Time {
value: String,
location: Location,
},
LocalTime {
value: String,
location: Location,
},
Date {
value: String,
location: Location,
},
NameIdentifier {
name: Vec<String>,
location: Location,
},
VariableReference {
reference: Box<AstNode>,
location: Location,
},
Array {
items: Vec<AstNode>,
location: Location,
},
Comparison {
lhs: Box<AstNode>,
rhs: Box<AstNode>,
comp: ComparisonType,
location: Location,
},
Logical {
lhs: Box<AstNode>,
rhs: Box<AstNode>,
comp: LogicalType,
location: Location,
},
Not {
rhs: Box<AstNode>,
location: Location,
},
Name {
prefix: Box<Option<AstNode>>,
value: Box<AstNode>,
location: Location,
},
ValueSelector {
lhs: Box<AstNode>,
rhs: Box<AstNode>,
location: Location,
},
MetadataSelector {
lhs: Box<AstNode>,
rhs: Box<Option<AstNode>>,
location: Location,
},
AttributeSelector {
lhs: Box<AstNode>,
rhs: Box<AstNode>,
location: Location,
},
MultiValueSelector {
lhs: Box<AstNode>,
rhs: Box<AstNode>,
location: Location,
},
InfixFunctionCall {
lhs: Box<AstNode>,
func: Box<AstNode>,
rhs: Box<AstNode>,
location: Location,
},
FunctionCall {
lhs: Box<AstNode>,
args: Vec<AstNode>,
location: Location,
},
IfElse {
condition: Box<AstNode>,
if_branch: Box<AstNode>,
else_branch: Box<AstNode>,
location: Location,
},
Default {
lhs: Box<AstNode>,
rhs: Box<AstNode>,
location: Location,
},
Document {
header: Option<Vec<AstNode>>,
body: Box<AstNode>,
location: Location,
},
VersionDirective {
version: String,
location: Location,
},
InputDirective {
name: Box<AstNode>,
data_format: Box<AstNode>,
options: Vec<AstNode>,
location: Location,
},
OutputDirective {
data_format: Box<AstNode>,
options: Vec<AstNode>,
location: Location,
},
Object {
fields: Vec<AstNode>,
location: Location,
},
KeyValuePair {
key: Box<AstNode>,
value: Box<AstNode>,
location: Location,
},
ConditionalKeyValuePair {
key: Box<AstNode>,
value: Box<AstNode>,
condition: Box<AstNode>,
location: Location,
},
StrInterpolation {
segments: Vec<AstNode>,
quote: Option<char>,
location: Location,
},
Option {
name: Box<AstNode>,
value: Box<AstNode>,
location: Location,
},
ContentType {
value: String,
location: Location,
},
ImportDirective {
module: Box<ImportedElement>,
elements: Vec<ImportedElement>,
location: Location,
},
NamespaceDirective {
prefix: Box<AstNode>,
uri: Box<AstNode>,
location: Location,
},
Uri {
value: String,
location: Location,
},
NullSafeSelector {
lhs: Box<AstNode>,
location: Location,
},
}
#[derive(Clone, PartialEq, Debug)]
pub struct ImportedElement {
pub name: Box<AstNode>,
pub alias: Option<AstNode>,
pub location: Location,
}
#[derive(Clone, PartialEq, Debug)]
pub enum ComparisonType {
Greater,
Less,
Equal,
NotEqual,
Similar,
GreaterEqual,
LessEqual,
}
#[derive(Clone, PartialEq, Debug)]
pub enum LogicalType {
And,
Or,
}
pub fn unescape_string(s: String, quotedby: &Option<char>) -> String {
if quotedby.is_none() {
s
} else {
let mut chars = s.chars();
let mut result = String::new();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.next().unwrap() {
'\\' => result.push('\\'),
'/' => result.push('/'),
'b' => result.push('\x08'),
'f' => result.push('\x0C'),
'n' => result.push('\n'),
'r' => result.push('\r'),
't' => result.push('\t'),
'$' => result.push('$'),
'u' => {
let mut codepoint = 0u32;
for _ in 0..4 {
let c = chars.next().unwrap();
codepoint *= 16;
codepoint += match c {
'0'..='9' => c as u32 - '0' as u32,
'a'..='f' => c as u32 - 'a' as u32 + 10,
'A'..='F' => c as u32 - 'A' as u32 + 10,
_ => panic!("Invalid unicode escape"),
};
}
result.push(char::from_u32(codepoint).unwrap());
}
a => {
if a == quotedby.unwrap() {
result.push(a)
} else {
result.push(c);
result.push(a)
}
}
}
} else {
result.push(c);
}
}
result
}
}
static KEYWORDS: &[&str; 28] = &[
"if", "else", "unless", "using", "---", "as", "is", "null", "true", "false", "default", "case",
"fun", "input", "output", "ns", "type", "import", "var", "and", "or",
"throw", "do", "for", "yield", "enum", "private", "async",
];
parser! {
pub grammar dw_parser() for str {
rule valid_start_name_ch() = quiet!{[ 'a'..='z' | 'A'..='Z']}
rule valid_name_ch() = quiet!{['_' | 'a'..='z' | 'A'..='Z' | '0'..='9']}
rule digit() = quiet!{['0' ..= '9']}
rule digit01() = quiet!{['0' ..= '1']}
rule digit02() = quiet!{['0' ..= '2']}
rule digit04() = quiet!{['0' ..= '4']}
rule digit12() = quiet!{['1' ..= '2']}
rule digit13() = quiet!{['1' ..= '3']}
rule digit15() = quiet!{['1' ..= '5']}
rule digit09() = quiet!{['0' ..= '9']}
rule digit19() = quiet!{['1' ..= '9']}
rule digit14() = quiet!{['1' ..= '4']}
rule digit03() = quiet!{['0' ..= '3']}
rule digit17() = quiet!{['1' ..= '7']}
rule any_date_literal_expr() -> AstNode =
e:any_date_expr() {e}
rule any_date_expr() -> AstNode =
period_literal_expr()
/ date_time_literal_expr()
/ time_literal_expr()
rule date_time_literal_expr() -> AstNode =
start:position!() "|" d:$(date()) t:$(time())? tz:$(time_zone())? "|" end:position!() {?
match (tz, t) {
(None, None) =>
Ok(AstNode::Date {
value:d.to_string(),
location: Location {
start,
end,
},
}),
(None, Some(t)) => {
let mut result = String::new();
result.push_str(d);
result.push_str(t);
Ok(AstNode::LocalDateTime {
value: result,
location: Location {
start,
end,
},
})
}
(Some(tz), Some(t)) => {
let mut result = String::new();
result.push_str(d);
result.push_str(t);
result.push_str(tz);
Ok(AstNode::DateTime {
value: result,
location: Location {
start,
end,
},
})
}
_ => Err("Invalid datetime expression. If timezone is present it needs a time.")
}
}
rule period_literal_expr() -> AstNode =
start:position!() "|" d:$(duration()) "|" end:position!() {
AstNode::Period {
value: d.to_string(),
location: Location {
start,
end,
}
}
}
rule time_literal_expr() -> AstNode =
start:position!() "|" te:$(local_time()) tz:$(time_zone())? "|" end:position!() {
if tz.is_none() {
AstNode::LocalTime {
value:te.to_string(),
location: Location {
start,
end,
},
}
} else{
let mut result = String::new();
result.push_str(te);
result.push_str(tz.unwrap());
AstNode::Time {
value: result,
location: Location {
start,
end,
},
}
}
}
rule time_zone() = ['Z'] / (['+'|'-'] time_zone_offset())
rule duration() = ['P'] (timeDuration() / dateDuration())
rule dateDuration() = (years())? (months())? (days())?
rule years() = (['-'])? (digit())+ ['Y']
rule months() = (['-'])? (digit())+ ['M']
rule days() = (['-']?) digit()+ ['D']
rule hours() = (['-'])? digit()+ ['H']
rule minutes() = (['-'])? digit()+ ['M']
rule timeDuration() = (days())? ['T'] (hours())? (minutes())? (seconds())?
rule seconds() = (['-'])? digit()+ (['.'] digit()+)? ['S']
rule time_zone_offset() = hh() [':'] mm()
rule time() = ['T'] local_time()
rule local_time() = hh() [':'] mm() ([':'] ss())?
rule date() = year() ['-']? (ordinal_date() / week_date() / calendar_date())
rule calendar_date() = mm() (['-'] day())?
rule week_date() = www() (['-'] dayOfWeek())?
rule ordinal_date() = ddd()
rule year() =
("-+"? digit() digit() digit() digit() digit()) / (digit() digit() digit() digit())
rule ddd() =
("00" digit19()) / (['0'] digit19() digit09()) / (digit13() digit09() digit09()) / (digit09() digit09() digit09())
rule hh() =
zt9() / (['1'] digit09()) / (['2'] digit04()) / expected!("Hours from 0 to 24")
rule mm() =
zt9() / (digit15() digit09()) / (digit09() digit09())
rule ss() =
(zt9() / (digit15() digit09() ) / "60") (['.'] digit09()+)?
rule zt9() =
"00" / ot9()
rule ot9() =
['0'] digit19()
rule day() =
ot9() / (digit12() digit09()) / (['3'] digit01()) / (digit09() digit09())
rule www() =
['W'] ((ot9() / (digit14() digit09()) / (['5'] digit03())) / (digit09() digit09()))
rule dayOfWeek() =
digit17()
/ digit09()
rule number_str() =
quiet!{['-']?['0'..='9']+ ("." ['0'..='9']+)?}
/ expected!("Number")
rule ws() =
quiet!{ [' ' | '\t' | '\n' | '\r']* comments()*}
rule fws() =
quiet!{ [' ' | '\t' | '\n' | '\r']+ comments()*}
rule comments() =
"//" (!['\n'][_])* ([' ' | '\t' | '\n' | '\r']*)?
/ "/*" (!"*/"[_])* "*/" ([' ' | '\t' | '\n' | '\r']*)?
rule name_identifier_str() =
quiet!{ valid_start_name_ch()valid_name_ch()* }
/ "++"
/ "--"
rule valid_name_identifier_str() -> String =
input:$(name_identifier_str()) {? if(KEYWORDS.contains(&input)) {Err("Identifier is a reserved word.")} else {Ok(String::from(input))} }
/ expected!("Identifier")
rule simple_name_identifier_expr() -> AstNode =
start:position!() s:valid_name_identifier_str() end:position!() {
AstNode::NameIdentifier{
name: vec![s],
location: Location {
start,
end,
}
}
}
rule name_identifier_expr() -> AstNode =
quiet!{ start:position!() s:valid_name_identifier_str() ++ ("::") end:position!() {
AstNode::NameIdentifier{
name: s,
location: Location {
start,
end,
},
}
}
}
/ expected!("Identifier")
rule variable_expr() -> AstNode =
start:position!() ident:name_identifier_expr() end:position!() {
AstNode::VariableReference {
reference: Box::new(ident),
location: Location {
start,
end,
}
}
}
rule double_quoted_str_expr() -> AstNode =
start:position!() "\"" segs:(double_quote_string_segment() / interpolation_segment() / interpolation_variable_ref_expr())* "\"" end:position!() {
if segs.is_empty() {
AstNode::Str {
value: "".to_string(),
quote: Some('"'),
location: Location {
start,
end,
}
}
} else if segs.len() == 1 {
match &segs[0] {
AstNode::Str {value, location, quote} => {
AstNode::Str {
value: value.to_string(),
quote: Some('\"'),
location: Location {
start,
end,
}
}
}
_ => {
AstNode::StrInterpolation {
segments: segs,
quote: Some('\"'),
location: Location {
start,
end,
}
}
}
}
} else {
AstNode::StrInterpolation {
segments: segs,
quote: Some('\"'),
location: Location {
start,
end,
}
}
}
}
rule dollar_expr() -> AstNode =
start:position!() name:$(['$']+) end:position!() {
AstNode::VariableReference {
reference: Box::new(
AstNode::NameIdentifier {
name: vec![name.to_string()],
location: Location {
start,
end,
}
}
),
location: Location {
start,
end,
}
}
}
rule dollar_named_variable_expr() -> AstNode =
"$" vr:variable_expr() {vr}
rule interpolation_variable_ref_expr() -> AstNode =
dollar_named_variable_expr() / dollar_expr()
rule interpolation_segment() -> AstNode = "$(" ws() e:expr() ws() ")" {e}
rule single_quote_string_segment() -> AstNode =
start:position!() s:$((!['\'' | '$' | '\\'][_] / "\\" [_] / "\\'" / "\\\\" / "\\$" / "\\b" / "\\f" / "\\n" / "\\r" / "\\t")+) end:position!() {
AstNode::Str {
value: s.to_string(),
quote: Some('\''),
location: Location {
start,
end,
}
}
}
rule double_quote_string_segment() -> AstNode =
start:position!() s:$((!['"' | '$' | '\\'][_] / "\\" [_] / "\\'" / "\\\\" / "\\$" / "\\b" / "\\f" / "\\n" / "\\r" / "\\t")+) end:position!() {
AstNode::Str {
value: s.to_string(),
quote: Some('\"'),
location: Location {
start,
end,
}
}
}
rule single_quoted_str_expr() -> AstNode =
start:position!() "'" segs:(single_quote_string_segment() / interpolation_segment() / interpolation_variable_ref_expr())* "'" end:position!() {
if segs.is_empty() {
AstNode::Str {
value: "".to_string(),
quote: Some('\''),
location: Location {
start,
end,
}
}
} else if segs.len() == 1 {
match &segs[0] {
AstNode::Str {value, location, quote} => {
AstNode::Str {
value: value.to_string(),
quote: Some('\''),
location: Location {
start,
end,
}
}
}
_ => {
AstNode::StrInterpolation {
segments: segs,
quote: Some('\''),
location: Location {
start,
end,
}
}
}
}
} else {
AstNode::StrInterpolation {
segments: segs,
quote: Some('\''),
location: Location {
start,
end,
}
}
}
}
rule string_expr() -> AstNode =
double_quoted_str_expr()
/ single_quoted_str_expr()
/ expected!("String")
rule number_expr() -> AstNode =
start:position!() n:$(number_str()) end:position!() {
AstNode::Number { value: n.to_string(), location: Location {start, end} }
}
rule null_expr() -> AstNode =
start:position!() "null" end:position!() {
AstNode::Null {
location: Location {
start,
end,
}
}
}
rule boolean_expr() -> AstNode =
start:position!() "true" end:position!() {
AstNode::Boolean {
value: true,
location: Location {
start,
end,
},
}
}
/ start:position!() "false" end:position!() {
AstNode::Boolean {
value: false,
location: Location {
start,
end,
},
}
}
/ expected!("Boolean")
rule array_expr() -> AstNode =
start:position!() "[" ws() v:value() ** ("," ws()) "]" end:position!() {
AstNode::Array {
items: v,
location: Location {
start,
end,
}
}
}
rule key_value_pair_expr() -> AstNode =
start:position!() key:(simple_name_identifier_expr() / string_expr()) ws() ":" ws() v:expr() end:position!() {
AstNode::KeyValuePair {
key: Box::new(key),
value: Box::new(v),
location: Location {
start,
end,
}
}
}
rule conditional_key_value_pair_expr() -> AstNode =
start:position!() "(" ws() key:(simple_name_identifier_expr() / string_expr() / enclosed_expr()) ws() ":" ws() v:expr() ws() ")" ws() "if" ws() condition:expr() end:position!() {
AstNode::ConditionalKeyValuePair {
key: Box::new(key),
value: Box::new(v),
condition: Box::new(condition),
location: Location {
start,
end,
}
}
}
rule object_expr() -> AstNode =
start:position!() "{" ws() kvp:(conditional_key_value_pair_expr() / key_value_pair_expr()) ** (ws() "," ws()) (",")? ws() "}" end:position!() {
AstNode::Object {
fields: kvp,
location: Location {
start,
end,
}
}
}
rule enclosed_expr() -> AstNode =
quiet!{"(" e:expr() ")" {e}}
rule literal_expr() -> AstNode =
quiet! {string_expr() / number_expr() / null_expr() / boolean_expr() / any_date_literal_expr()}
/ expected!("Literal Expression")
rule value() -> AstNode =
ws() v:(literal_expr() / variable_expr() / object_expr() / array_expr() / enclosed_expr()) ws() {v}
rule if_else_expr() -> AstNode =
start:position!() "if" ws() "(" cond:expr() ")" if_e:expr() "else" else_e:expr() end:position!() {
AstNode::IfElse {
condition: Box::new(cond),
if_branch: Box::new(if_e),
else_branch:Box::new(else_e),
location: Location {
start,
end,
},
}
}
rule not_exclamation_expr() -> AstNode =
start:position!() "!" v:unary_expr() end:position!() {
AstNode::Not {
rhs: Box::new(v),
location: Location {
start,
end,
},
}
}
rule not_word_expr() -> AstNode =
quiet!{
start:position!() "not" v:boolean_or_expr() end:position!() {
AstNode::Not {
rhs:Box::new(v),
location: Location {
start,
end,
}
}
}
}
rule name_string_expr() -> AstNode =
start:position!() s:valid_name_identifier_str() end:position!() {
AstNode::Str{
value: s,
quote: None,
location: Location {
start,
end,
}
}
}
rule prefix_expression() -> AstNode =
prefix:name_identifier_expr() "#" {prefix}
rule name_expr() -> AstNode =
start:position!()
prefix:prefix_expression()?
name:(name_string_expr()/ string_expr())
end:position!() {
AstNode::Name {
prefix: Box::new(prefix),
value: Box::new(name),
location: Location {
start,
end,
}
}
}
#[cache_left_rec]
rule basic_expr() -> AstNode =
start:position!() v:basic_expr() "(" args:(expr() ** ",") ")" end:position!() {
AstNode::FunctionCall {
lhs: Box::new(v),
args,
location: Location {
start,
end,
}
}
}
/ start:position!() v:basic_expr() ws() "." name:(name_expr()) end:position!() {
AstNode::ValueSelector {
lhs: Box::new(v),
rhs: Box::new(name),
location: Location {
start,
end,
},
}
}
/ start:position!() v:basic_expr() ws() ".@" name:(name_expr()) end:position!() {
AstNode::AttributeSelector {
lhs: Box::new(v),
rhs: Box::new(name),
location: Location {
start,
end,
},
}
}
/ start:position!() v:basic_expr() ws() ".*" name:(name_expr()) end:position!() {
AstNode::MultiValueSelector {
lhs: Box::new(v),
rhs: Box::new(name),
location: Location {
start,
end,
},
}
}
/ start:position!() v:basic_expr() ws() ".^" name:(name_expr()?) end:position!() {
AstNode::MetadataSelector {
lhs: Box::new(v),
rhs: Box::new(name),
location: Location {
start,
end,
},
}
}
/ start:position!() v:basic_expr() ws() "[" name:expr() "]" end:position!() {
AstNode::ValueSelector {
lhs:Box::new(v),
rhs: Box::new(name),
location: Location {
start,
end,
}
}
}
/ value()
rule unary_expr() -> AstNode =
not_exclamation_expr()
/ not_word_expr()
/ if_else_expr()
/ basic_expr()
rule additive_expr() -> AstNode = unary_expr()
#[cache_left_rec]
rule relational_expr() -> AstNode =
start:position!() l:additive_expr() ws() "<=" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::LessEqual, location: Location {start, end}}}
/ start:position!() l:additive_expr() ws() ">=" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::GreaterEqual, location: Location {start, end}}}
/ start:position!() l:additive_expr() ws() ">" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Greater, location: Location {start, end}}}
/ start:position!() l:additive_expr() ws() "<" ws() r:relational_expr() end:position!() {AstNode::Comparison {lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Less, location: Location {start, end}}}
/ additive_expr()
#[cache_left_rec]
rule equality_expr() -> AstNode =
start:position!() l:equality_expr() ws() "==" ws() r:relational_expr() end:position!() {AstNode::Comparison{lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Equal, location: Location {start, end}}}
/ start:position!() l:equality_expr() ws() "!=" ws() r:relational_expr() end:position!() {AstNode::Comparison{lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::NotEqual, location: Location {start, end}}}
/ start:position!() l:equality_expr() ws() "=" ws() r:relational_expr() end:position!() {AstNode::Comparison{lhs: Box::new(l), rhs: Box::new(r), comp: ComparisonType::Similar, location: Location {start, end}}}
/ relational_expr()
#[cache_left_rec]
rule boolean_and_expr() -> AstNode =
start:position!() l:boolean_and_expr() ws() "and" ws() r:equality_expr() end:position!() {
AstNode::Logical {
lhs: Box::new(l),
rhs: Box::new(r),
comp: LogicalType::And,
location: Location {
start,
end,
},
}
}
/ equality_expr()
#[cache_left_rec]
rule boolean_or_expr() -> AstNode =
start:position!() l:boolean_or_expr() ws() "or" ws() r:boolean_and_expr() end:position!() {
AstNode::Logical {
lhs: Box::new(l),
rhs: Box::new(r),
comp: LogicalType::Or,
location: Location {
start,
end,
}
}
}
/ boolean_and_expr()
#[cache_left_rec]
rule high_level_expr() -> AstNode =
start:position!() l:high_level_expr() ws() "default" ws() r:boolean_or_expr() end:position!() {
AstNode::Default {
lhs: Box::new(l),
rhs: Box::new(r),
location: Location {
start,
end
}
}
}
/ start:position!() l:high_level_expr() ws() vr:variable_expr() ws() r:boolean_or_expr() end:position!() {
AstNode::InfixFunctionCall {
lhs: Box::new(l),
func: Box::new(vr),
rhs:Box::new(r),
location: Location {
start,
end,
}
}
}
/ boolean_or_expr()
rule expr() -> AstNode = precedence! {
x:@ ws() "!" end:position!() {
let start = x.location().start;
AstNode::NullSafeSelector {
lhs: Box::new(x),
location: Location {
start,
end,
},
}
}
--
ws() hle:high_level_expr() ws() {hle}
}
rule uri() -> AstNode =
start:position!() s:$(([^ ' ' | '\n' | '\r' | '\t']+)) end:position!() {
AstNode::Uri {
value: s.to_string(),
location: Location {
start,
end,
}
}
}
rule namespace_directive_expr() -> AstNode =
start:position!() "ns" ws() prefix:name_identifier_expr() ws() ns:uri() end:position!() {
AstNode::NamespaceDirective {
prefix: Box::new(prefix),
uri: Box::new(ns),
location: Location {
start,
end,
}
}
}
rule import_directive_expr() -> AstNode =
import_from_expr()
/ import_module_expr()
rule import_from_expr() -> AstNode =
start:position!() "import" ws() ie: imported_elements() ws() "from" module:imported_element_module() end:position!() {
AstNode::ImportDirective {
module: Box::new(module),
elements: ie,
location: Location {
start,
end,
}
}
}
rule import_module_expr() -> AstNode =
start:position!() "import" ws() module:imported_element_module() end:position!() {
AstNode::ImportDirective {
module: Box::new(module),
elements: vec![],
location: Location {
start,
end,
}
}
}
rule import_alias() -> AstNode =
ws() "as" ws() ni:simple_name_identifier_expr() {ni}
rule imported_element_module() -> ImportedElement =
start:position!() ws() module:name_identifier_expr() ni:(import_alias())? end:position!() {
ImportedElement {
name: Box::new(module),
alias: ni ,
location: Location {
start,
end,
}
}
}
rule imported_elements() -> Vec<ImportedElement> =
imported_element() ++ ","
rule imported_element() -> ImportedElement =
star_imported_element()
/ element_imported_element()
rule element_imported_element() -> ImportedElement =
start:position!() module:simple_name_identifier_expr() ni:(import_alias())? end:position!() {
ImportedElement {
name: Box::new(module),
alias: ni ,
location: Location {
start,
end,
}
}
}
rule star_imported_element() -> ImportedElement =
start:position!() s:star_name_identifier_expr() end:position!(){
ImportedElement {
name: Box::new(s),
alias: None ,
location: Location {
start,
end,
}
}
}
rule star_name_identifier_expr() -> AstNode =
start:position!() n:$(['*']) end:position!() {
AstNode::NameIdentifier {
name: vec![n.to_string()],
location: Location {
start,
end,
}
}
}
rule version_directive_expr() -> AstNode =
start:position!() "%dw" ws() mayor:$(digit()) ['.'] minor:$(digit()) end:position!() {
let mut result = String::new();
result.push_str(mayor);
result.push('.');
result.push_str(minor);
AstNode::VersionDirective {
version: result,
location: Location {
start,
end,
}
}
}
rule option_expr() -> AstNode =
start:position!() name:simple_name_identifier_expr() ws() "=" ws() value:literal_expr() end:position!() {
AstNode::Option {
name: Box::new(name),
value: Box::new(value),
location: Location {
start,
end,
},
}
}
rule input_directive_expr() -> AstNode =
start:position!() "input" ws() name:(simple_name_identifier_expr()) ws() df:(content_type_expr() / simple_name_identifier_expr()) ws() options:(option_expr() ** ",") end:position!() {
AstNode::InputDirective {
name: Box::new(name),
data_format: Box::new(df),
options,
location: Location {
start,
end,
}
}
}
rule output_directive_expr() -> AstNode =
start:position!() "output" ws() df:(content_type_expr()/ simple_name_identifier_expr()) ws() options:(option_expr() ** ",") end:position!() {
AstNode::OutputDirective {
data_format: Box::new(df),
options,
location: Location {start, end}
}
}
rule content_type() =
quiet!{
['a'..='z' | 'A'..='Z' | '0'..='9']+ "/" ['a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '+' | '-']+
}
rule content_type_expr() -> AstNode =
start:position!() ct:$(content_type()) end:position!() {
AstNode::ContentType {
value: ct.to_string(),
location: Location {start, end}
}
}
rule directive() -> AstNode =
ws() vd:version_directive_expr() {vd}
/ ws() id:import_directive_expr() {id}
/ ws() nd:namespace_directive_expr() {nd}
/ ws() id: input_directive_expr() {id}
/ ws() od: output_directive_expr() {od}
rule document() -> AstNode =
start:position!() h:(directive()++ fws())? ws() "---"? b:expr() end:position!() {
AstNode::Document {
header: h,
body: Box::new(b),
location: Location {
start,
end,
}
}
}
pub rule parse() -> AstNode = e:document() { e }
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum MessageKind {
Warning,
Error,
}
pub fn parse_dw(_name: &str, input: &str) -> Result<DWParseResult, DWError> {
let result = dw_parser::parse(input);
match result {
Ok(node) => Ok(DWParseResult {
ast: node,
messages: vec![],
}),
Err(err) => {
let message = Message {
message: format!("Expecting {}", err.expected),
kind: MessageKind::Error,
location: Location {
start: err.location.offset,
end: err.location.offset,
},
};
Err(DWError {
messages: vec![message],
})
}
}
}