use indexmap::IndexMap;
use super::cursor::Cursor;
use crate::ast::{Function, PathSegment, Value, Variable};
impl Cursor<'_> {
pub(crate) fn value(&mut self) -> Option<Value> {
if !self.enter_value() {
return None;
}
let value = self.value_alternatives();
self.leave_value();
value
}
fn value_alternatives(&mut self) -> Option<Value> {
if let Some(value) = self.value_null() {
return Some(value);
}
if let Some(value) = self.value_boolean() {
return Some(value);
}
if let Some(value) = self.value_string() {
return Some(value);
}
if let Some(value) = self.value_number() {
return Some(value);
}
if let Some(value) = self.value_array() {
return Some(value);
}
if let Some(value) = self.value_hash() {
return Some(value);
}
if let Some(value) = self.function() {
return Some(value);
}
self.variable()
}
fn value_null(&mut self) -> Option<Value> {
self.enter_named("null");
let matched = self.literal("null");
self.leave_named();
matched.then_some(Value::Null)
}
fn value_boolean(&mut self) -> Option<Value> {
self.enter_named("boolean");
let value = if self.literal("true") {
Some(Value::Boolean(true))
} else if self.literal("false") {
Some(Value::Boolean(false))
} else {
None
};
self.leave_named();
value
}
fn value_string(&mut self) -> Option<Value> {
self.string_literal().map(Value::String)
}
fn string_literal(&mut self) -> Option<String> {
let start = self.pos();
self.enter_named("string");
let text = self.string_literal_body();
self.leave_named();
if text.is_none() {
self.reset(start);
}
text
}
fn string_literal_body(&mut self) -> Option<String> {
if !self.literal("\"") {
return None;
}
let mut text = String::new();
while let Some(ch) = self.string_char() {
text.push(ch);
}
if !self.literal("\"") {
return None;
}
Some(text)
}
fn string_char(&mut self) -> Option<char> {
match self.peek()? {
'\\' => {
let start = self.pos();
self.advance();
let unescaped = match self.peek() {
Some('"') => Some('"'),
Some('\\') => Some('\\'),
Some('n') => Some('\n'),
Some('r') => Some('\r'),
Some('t') => Some('\t'),
_ => None,
};
if let Some(ch) = unescaped {
self.advance();
return Some(ch);
}
self.reset(start);
None
}
'"' => None,
ch if (ch as u32) <= 0x1f => None,
ch => {
self.advance();
Some(ch)
}
}
}
fn value_number(&mut self) -> Option<Value> {
let start = self.pos();
self.enter_named("number");
let matched = self.value_number_body();
self.leave_named();
if !matched {
self.reset(start);
return None;
}
let text = self.slice(start, self.pos());
if let Ok(number) = text.parse::<f64>() {
return Some(Value::Number(number));
}
self.reset(start);
None
}
fn value_number_body(&mut self) -> bool {
self.literal("-");
if !self.digit() {
return false;
}
while self.digit() {}
let mark = self.pos();
if self.literal(".") {
if !self.digit() {
self.reset(mark);
return true;
}
while self.digit() {}
}
true
}
fn value_array(&mut self) -> Option<Value> {
let start = self.pos();
if !self.literal("[") {
self.reset(start);
return None;
}
self.whitespace_star();
let mut items = Vec::new();
let mark = self.pos();
if let Some(head) = self.value() {
items.push(head);
loop {
let tail = self.pos();
self.whitespace_star();
if !self.literal(",") {
self.reset(tail);
break;
}
self.whitespace_star();
let Some(item) = self.value() else {
self.reset(tail);
break;
};
items.push(item);
}
self.trailing_comma();
} else {
self.reset(mark);
}
self.whitespace_star();
if !self.literal("]") {
self.reset(start);
return None;
}
Some(Value::Array(items))
}
fn value_hash(&mut self) -> Option<Value> {
let start = self.pos();
if !self.literal("{") {
self.reset(start);
return None;
}
self.whitespace_star();
let mut hash = IndexMap::new();
let mark = self.pos();
if let Some(head) = self.value_hash_item() {
if let Some((key, value)) = head {
hash.insert(key, value);
}
loop {
let tail = self.pos();
self.whitespace_star();
if !self.literal(",") {
self.reset(tail);
break;
}
self.whitespace_star();
let Some(item) = self.value_hash_item() else {
self.reset(tail);
break;
};
if let Some((key, value)) = item {
hash.insert(key, value);
}
}
self.trailing_comma();
} else {
self.reset(mark);
}
self.whitespace_star();
if !self.literal("}") {
self.reset(start);
return None;
}
Some(Value::Hash(hash))
}
#[allow(
clippy::option_option,
reason = "outer None is a parse failure, \
inner None is a `$$mdtype` entry that parsed and was discarded; \
collapsing them would lose the difference between the two"
)]
fn value_hash_item(&mut self) -> Option<Option<(String, Value)>> {
let start = self.pos();
let key = if let Some(identifier) = self.identifier() {
identifier.to_string()
} else if let Some(key) = self.string_literal() {
key
} else {
self.reset(start);
return None;
};
if !self.literal(":") {
self.reset(start);
return None;
}
self.whitespace_star();
let Some(value) = self.value() else {
self.reset(start);
return None;
};
if key == "$$mdtype" {
return Some(None);
}
Some(Some((key, value)))
}
fn trailing_comma(&mut self) {
let start = self.pos();
self.whitespace_star();
if !self.literal(",") {
self.reset(start);
}
}
pub(crate) fn function(&mut self) -> Option<Value> {
let start = self.pos();
let Some(name) = self.identifier() else {
self.reset(start);
return None;
};
if !self.literal("(") {
self.reset(start);
return None;
}
self.whitespace_star();
let head = self.function_parameter();
let mut tail = Vec::new();
loop {
let mark = self.pos();
self.whitespace_star();
if !self.literal(",") {
self.reset(mark);
break;
}
self.whitespace_star();
let Some(parameter) = self.function_parameter() else {
self.reset(mark);
break;
};
tail.push(parameter);
}
if !self.literal(")") {
self.reset(start);
return None;
}
let mut parameters = IndexMap::new();
if let Some(head) = head {
for (index, (name, value)) in std::iter::once(head).chain(tail).enumerate() {
let key = name.unwrap_or_else(|| Function::positional_key(index));
parameters.insert(key, value);
}
}
Some(Value::Function(Function::new(name.to_string(), parameters)))
}
fn function_parameter(&mut self) -> Option<(Option<String>, Value)> {
let start = self.pos();
let name = {
let mark = self.pos();
match self.identifier() {
Some(identifier) if self.literal("=") => Some(identifier.to_string()),
_ => {
self.reset(mark);
None
}
}
};
let Some(value) = self.value() else {
self.reset(start);
return None;
};
Some((name, value))
}
pub(crate) fn variable(&mut self) -> Option<Value> {
let start = self.pos();
self.enter_named("variable");
let value = self.variable_body();
self.leave_named();
if value.is_none() {
self.reset(start);
}
value
}
fn variable_body(&mut self) -> Option<Value> {
let Some(prefix @ ('$' | '@')) = self.peek() else {
return None;
};
self.advance();
let head = self.identifier()?;
let mut path = vec![PathSegment::Key(head.to_string())];
while let Some(segment) = self.variable_tail() {
path.push(segment);
}
if prefix == '@' {
let steps = path
.into_iter()
.map(|segment| match segment {
PathSegment::Key(key) => Value::String(key),
PathSegment::Index(index) => Value::Number(index),
})
.collect();
return Some(Value::Array(steps));
}
Some(Value::Variable(Variable::new(path)))
}
fn variable_tail(&mut self) -> Option<PathSegment> {
let start = self.pos();
if self.literal(".") {
if let Some(name) = self.identifier() {
return Some(PathSegment::Key(name.to_string()));
}
self.reset(start);
return None;
}
if !self.literal("[") {
self.reset(start);
return None;
}
let segment = if let Some(Value::Number(index)) = self.value_number() {
PathSegment::Index(index)
} else if let Some(key) = self.string_literal() {
PathSegment::Key(key)
} else {
self.reset(start);
return None;
};
if !self.literal("]") {
self.reset(start);
return None;
}
Some(segment)
}
}