mod art_block;
mod attrs;
mod blocks;
mod status;
mod variant;
#[cfg(test)]
mod divergence_tests;
#[cfg(test)]
mod golden_tests;
use crate::types::{ArtDescriptor, ArtParseError, ArtParseOptions, ArtParseResult, SourceLocation};
use vize_s0::Allocator;
pub fn parse_art<'a>(
allocator: &'a Allocator,
source: &'a str,
options: ArtParseOptions,
) -> ArtParseResult<'a> {
let filename: &'a str = if options.filename.is_empty() {
""
} else {
allocator.alloc_str(&options.filename)
};
let blocks = blocks::split_blocks(allocator, source)?;
let frame = blocks.art.ok_or(ArtParseError::NoArtBlock)?;
let (tree, _surface_errors) = vize_s1::parse(allocator, frame.source());
let art = art_block::art_element(&tree).ok_or(ArtParseError::NoArtBlock)?;
let define_art = blocks
.script_setup
.as_ref()
.and_then(|script| parse_define_art_metadata(allocator, script.content));
let (metadata, _warnings) =
art_block::parse_metadata(allocator, &art.open, define_art.as_ref(), filename)?;
let variants = variant::parse_variants(allocator, &art.children, frame)?;
Ok(ArtDescriptor {
filename,
source,
metadata,
variants,
script_setup: blocks.script_setup,
script: blocks.script,
styles: blocks.styles,
})
}
pub(crate) fn art_status_warnings<'a>(
allocator: &'a Allocator,
source: &'a str,
filename: &str,
) -> vize_s0::Vec<'a, &'a str> {
let empty = || vize_s0::Vec::new_in(&allocator);
let Some(frame) = blocks::split_blocks(allocator, source)
.ok()
.and_then(|blocks| blocks.art)
else {
return empty();
};
let (tree, _surface_errors) = vize_s1::parse(allocator, frame.source());
let Some(art) = art_block::art_element(&tree) else {
return empty();
};
match art_block::parse_metadata(allocator, &art.open, None, filename) {
Ok((_, warnings)) => warnings,
Err(_) => empty(),
}
}
#[derive(Debug)]
pub(crate) struct DefineArtMetadata<'a> {
pub component_name: Option<&'a str>,
pub component: Option<&'a str>,
pub title: Option<&'a str>,
pub description: Option<&'a str>,
pub category: Option<&'a str>,
pub tags: vize_s0::Vec<'a, &'a str>,
pub status: Option<&'a str>,
pub order: Option<u32>,
}
impl<'a> DefineArtMetadata<'a> {
fn new(allocator: &'a Allocator) -> Self {
Self {
component_name: None,
component: None,
title: None,
description: None,
category: None,
tags: vize_s0::Vec::new_in(&allocator),
status: None,
order: None,
}
}
}
fn parse_define_art_metadata<'a>(
allocator: &'a Allocator,
script: &'a str,
) -> Option<DefineArtMetadata<'a>> {
if !script.contains("defineArt") {
return None;
}
let art = vize_croquis::script_parser::parse_define_art(script)?;
Some(define_art_metadata(allocator, &art))
}
pub(crate) fn define_art_metadata<'a>(
allocator: &'a Allocator,
art: &vize_croquis::macros::ArtDefinition,
) -> DefineArtMetadata<'a> {
let mut meta = DefineArtMetadata::new(allocator);
meta.component_name = Some(allocator.alloc_str(art.component_name.as_str()));
meta.component = art
.component_source
.as_ref()
.map(|source| allocator.alloc_str(source.as_str()));
meta.title = art
.title
.as_ref()
.map(|value| allocator.alloc_str(value.as_str()));
meta.description = art
.description
.as_ref()
.map(|value| allocator.alloc_str(value.as_str()));
meta.category = art
.category
.as_ref()
.map(|value| allocator.alloc_str(value.as_str()));
meta.status = art
.status
.as_ref()
.map(|value| allocator.alloc_str(value.as_str()));
meta.order = art.order;
for tag in &art.tags {
meta.tags.push(allocator.alloc_str(tag.as_str()));
}
meta
}
pub(crate) fn calculate_location_fast(source: &str, start: u32, end: u32) -> SourceLocation {
let before = prefix(source, start);
let line_start = before
.iter()
.rposition(|&byte| byte == b'\n')
.map_or(0, |at| at + 1);
let column = (before.len() - line_start) as u32;
SourceLocation::new(start, end, line_of(source, start), column)
}
pub(crate) fn line_of(source: &str, offset: u32) -> u32 {
prefix(source, offset)
.iter()
.filter(|&&byte| byte == b'\n')
.count() as u32
+ 1
}
fn prefix(source: &str, offset: u32) -> &[u8] {
let bytes = source.as_bytes();
bytes.get(..offset as usize).unwrap_or(bytes)
}
#[cfg(test)]
mod tests;