use super::*;
pub(crate) fn parse_attr(pair: Pair<Rule>) -> Result<Attr, ParseError> {
let span = Span::from(&pair.as_span());
let mut key = String::new();
let mut value = AttrValue::AttrString(String::new());
for inner in pair.into_inner() {
if inner.as_rule() == Rule::key_attr {
for kv_inner in inner.into_inner() {
match kv_inner.as_rule() {
Rule::id => {
key = kv_inner.as_str().to_string();
}
Rule::number_literal => {
value = parse_attr_number(kv_inner.as_str());
}
Rule::string_contents | Rule::string_blank => {
value = AttrValue::String(kv_inner.as_str().to_string());
}
Rule::attr_string => {
value = AttrValue::AttrString(kv_inner.as_str().to_string());
}
_ => {}
}
}
}
}
Ok(Attr { key, value, span })
}
pub(crate) fn parse_attr_number(s: &str) -> AttrValue {
let normalized = normalize_number_str(s);
if normalized.contains('.') {
AttrValue::Float(normalized.parse().unwrap_or(0.0))
} else {
AttrValue::Integer(normalized.parse().unwrap_or(0))
}
}
pub(crate) fn parse_key_words(pair: Pair<Rule>) -> Result<KeyWords, ParseError> {
let span = Span::from(&pair.as_span());
let mut names = Vec::new();
let mut words = Vec::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::key_list => {
for key_inner in inner.into_inner() {
if key_inner.as_rule() == Rule::id {
names.push(key_inner.as_str().to_string());
}
}
}
Rule::words => {
for word_inner in inner.into_inner() {
match word_inner.as_rule() {
Rule::string_contents | Rule::string_blank => {
words.push(word_inner.as_str().to_string());
}
Rule::word_nofenced => {
words.push(word_inner.as_str().to_string());
}
Rule::sakura_script => {
words.push(word_inner.as_str().to_string());
}
_ => {}
}
}
}
_ => {}
}
}
Ok(KeyWords { names, words, span })
}
pub(crate) fn parse_code_block(pair: Pair<Rule>) -> Result<CodeBlock, ParseError> {
let span = Span::from(&pair.as_span());
let mut language = None;
let mut content = String::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::id => {
language = Some(inner.as_str().to_string());
}
Rule::code_contents => {
content = inner.as_str().to_string();
}
_ => {}
}
}
Ok(CodeBlock {
language,
content,
span,
})
}
pub(crate) fn parse_var_set(pair: Pair<Rule>) -> Result<VarSet, ParseError> {
let span = Span::from(&pair.as_span());
let scope = match pair.as_rule() {
Rule::var_set_global => VarScope::Global,
Rule::var_set_property => VarScope::Property,
_ => VarScope::Local, };
let mut name: Option<String> = None;
let mut terms: Vec<Expr> = Vec::new();
let mut operators: Vec<BinOp> = Vec::new();
let mut word_ref_name: Option<String> = None;
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::id | Rule::property_id => {
if name.is_none() {
name = Some(inner.as_str().to_string());
}
}
Rule::word_ref => {
for word_inner in inner.into_inner() {
if word_inner.as_rule() == Rule::id {
word_ref_name = Some(word_inner.as_str().to_string());
break;
}
}
}
rule => {
if let Some(op) = bin_op_from_rule(rule) {
operators.push(op);
} else if let Some(expr) = try_parse_expr(inner) {
terms.push(expr);
}
}
}
}
let value = if let Some(ref_name) = word_ref_name {
SetValue::WordRef { name: ref_name }
} else {
let expr = if terms.is_empty() {
Expr::BlankString
} else {
build_left_assoc_expr(terms, operators)
};
SetValue::Expr(expr)
};
Ok(VarSet {
name,
scope,
value,
span,
})
}
pub(crate) fn normalize_number_str(s: &str) -> String {
s.chars()
.map(|c| match c {
'0'..='9' => ((c as u32 - '0' as u32) + '0' as u32) as u8 as char,
'-' => '-',
'.' => '.',
_ => c,
})
.collect()
}