mod animation;
mod chain;
mod font;
mod group;
mod import;
mod lettering;
mod lex;
mod scenes;
mod screen;
mod sprite;
mod texture;
mod themes;
mod tiles;
mod widgets;
use std::collections::HashMap;
use crate::lint::{Def, Detail, Kind, Sources};
use crate::{Color, Document, Error, ErrorCode, Limits, LintCode, Palette, Position, Sprite};
use animation::AnimationDef;
use import::Files;
pub(crate) use import::Loader;
pub(crate) fn scene_chain_name(source: &str, limits: &Limits) -> crate::Result<String> {
Ok(chain::parse(
lex::Token {
text: source,
pos: Position::new(1, 1),
},
limits,
)?
.canonical())
}
pub(crate) fn scene_chain(
source: &str,
doc: &Document,
limits: &Limits,
budget: &mut crate::scene::Budget,
) -> crate::Result<(String, Sprite)> {
let chain = chain::parse(
lex::Token {
text: source,
pos: Position::new(1, 1),
},
limits,
)?;
let sprite = doc.chain_base(chain.base.text).ok_or_else(|| {
if chain.base.text.contains('.') {
let e = tiles::unknown_document_base(chain.base, doc);
Error::new(e.code, e.message)
} else {
Error::new(ErrorCode::UnknownName, "Unknown background sprite")
}
})?;
let sprite = chain.apply_scene(
sprite,
&doc.palettes,
limits,
&mut chain::PaletteUses::new(),
budget,
)?;
Ok((chain.canonical(), sprite))
}
use lex::{Node, Token};
use sprite::SpriteDef;
use tiles::{TilemapDef, TilesetDef};
const DIRECTIVES: &str = "`import`, `palette`, `sprite`, `texture`, `animation`, `tileset`, \
`tilemap`, `font`, `text`, `states`, `icons`, `theme`, `screen`, `surface`, `background`, `group` or `lint`";
pub(crate) fn parse(
text: &str,
limits: &Limits,
loader: Option<&mut Loader>,
) -> (Document, Vec<Error>) {
let mut errors = Vec::new();
if text.len() > limits.max_text_bytes {
errors.push(Error::at(
Position::new(1, 1),
ErrorCode::LimitExceeded,
format!(
"The text is {} bytes long, more than the limit of {}",
text.len(),
limits.max_text_bytes
),
));
return (Document::default(), errors);
}
let files = Files::load(text, limits, loader, &mut errors);
let lexed: Vec<Vec<Node>> = files
.texts
.iter()
.enumerate()
.map(|(i, t)| lex::lex(t, i as u32, &mut errors))
.collect();
let mut generated_names = HashMap::new();
let mut name_bytes = 0usize;
for node in lexed
.iter()
.flatten()
.filter(|n| matches!(n.keyword(), "states" | "icons"))
{
if let Some(prefix) = node.line.tokens.get(1) {
for child in &node.children {
let index = usize::from(node.keyword() == "icons");
if let Some(name) = child.line.tokens.get(index) {
let length = prefix.text.len() + 1 + name.text.len();
if generated_names.len() >= limits.max_definitions
|| length > limits.max_text_bytes.saturating_sub(name_bytes)
{
continue;
}
name_bytes += length;
generated_names.insert(name.pos, format!("{}.{}", prefix.text, name.text));
}
}
}
}
let mut parser = Parser {
limits,
errors,
palettes: Vec::new(),
sprites: Vec::new(),
animations: Vec::new(),
tilesets: Vec::new(),
tilemaps: Vec::new(),
fonts: Vec::new(),
failed_fonts: Vec::new(),
allows: Vec::new(),
file_allows: Vec::new(),
layer_counts: HashMap::new(),
groups: Vec::new(),
palette_uses: chain::PaletteUses::new(),
tile_sprites: Vec::new(),
tileset_tiles: Vec::new(),
generated_names: &generated_names,
widgets: Vec::new(),
extra_definitions: 0,
definitions_seen: 0,
themes: Vec::new(),
screens: Vec::new(),
surfaces: Vec::new(),
backgrounds: Vec::new(),
scene_budget: crate::scene::Budget::new(limits),
};
let mut walk = Walk {
lexed: &lexed,
targets: &files.targets,
walked: vec![false; lexed.len()],
count: 0,
};
walk.walked[0] = true;
walk.file(&mut parser, 0);
let mut document = parser.resolve();
document.sources = parser.sources(&files.names);
let mut errors = parser.errors;
errors.sort_by_key(|e| e.position);
for e in &mut errors {
if let Some(p) = e.position.filter(|p| p.file > 0) {
e.file = files.names.get(p.file as usize).cloned();
}
}
(document, errors)
}
struct Walk<'n, 'a> {
lexed: &'n [Vec<Node<'a>>],
targets: &'n HashMap<(u32, usize), u32>,
walked: Vec<bool>,
count: usize,
}
impl<'a> Walk<'_, 'a> {
fn file(&mut self, parser: &mut Parser<'a, '_>, file: u32) -> bool {
let lexed = self.lexed;
for node in &lexed[file as usize] {
if node.keyword() == "import" {
let line = node.line.first().pos.line;
if let Some(&target) = self.targets.get(&(file, line)) {
if !self.walked[target as usize] {
self.walked[target as usize] = true;
if !self.file(parser, target) {
return false;
}
}
}
continue;
}
if self.count + parser.extra_definitions >= parser.limits.max_definitions {
parser.errors.push(node.line.first().error(
ErrorCode::LimitExceeded,
format!(
"More than the limit of {} definitions",
parser.limits.max_definitions
),
));
return false;
}
self.count += 1;
parser.definitions_seen = self.count;
if let Err(e) = parser.definition(node) {
parser.errors.push(e);
}
}
true
}
}
type Parsed<T> = std::result::Result<T, Error>;
type SetDef<'a> = (Kind, Token<'a>, Vec<crate::lint::Member>);
struct PaletteDef<'a> {
name: Token<'a>,
from: Option<Token<'a>>,
colors: Vec<(char, Color)>,
positions: Vec<Position>,
ramps: Vec<RampDef<'a>>,
}
struct RampDef<'a> {
name: Token<'a>,
keys: Token<'a>,
}
impl RampDef<'_> {
fn keys(&self) -> Vec<(char, Position)> {
self.keys
.text
.chars()
.enumerate()
.map(|(i, k)| (k, self.keys.at(i)))
.collect()
}
}
struct Parser<'a, 'l> {
surfaces: Vec<scenes::SurfaceDef<'a>>,
backgrounds: Vec<scenes::BackgroundDef<'a>>,
scene_budget: crate::scene::Budget,
screens: Vec<screen::ScreenDef<'a>>,
themes: Vec<themes::ThemeDef<'a>>,
generated_names: &'a HashMap<Position, String>,
widgets: Vec<widgets::WidgetDef<'a>>,
extra_definitions: usize,
definitions_seen: usize,
limits: &'l Limits,
errors: Vec<Error>,
palettes: Vec<PaletteDef<'a>>,
sprites: Vec<SpriteDef<'a>>,
animations: Vec<AnimationDef<'a>>,
tilesets: Vec<TilesetDef<'a>>,
tilemaps: Vec<TilemapDef<'a>>,
fonts: Vec<(Token<'a>, crate::Font)>,
failed_fonts: Vec<Token<'a>>,
allows: Vec<(Kind, Token<'a>, Vec<LintCode>)>,
file_allows: Vec<(u32, Vec<LintCode>)>,
layer_counts: HashMap<Position, Vec<usize>>,
groups: Vec<group::GroupDef<'a>>,
palette_uses: chain::PaletteUses,
tile_sprites: Vec<Sprite>,
tileset_tiles: Vec<(String, Vec<String>)>,
}
struct Header<'a> {
name: Token<'a>,
args: Vec<Token<'a>>,
options: Vec<(Token<'a>, Token<'a>)>,
}
impl<'a> Header<'a> {
fn new(node: &Node<'a>, what: &str) -> Parsed<Self> {
let tokens = &node.line.tokens;
let name = *tokens
.get(1)
.ok_or_else(|| tokens[0].end_error(format!("Expected a name for the {what}")))?;
if what == "sprite" {
check_sprite_name(name)?;
} else {
check_name(name)?;
}
let mut args = Vec::new();
let mut options = Vec::new();
for &token in &tokens[2..] {
match token.text.split_once('=') {
Some((key, value)) if !key.is_empty() => {
let key_token = Token {
text: key,
pos: token.pos,
};
let value_token = Token {
text: value,
pos: token.at(key.chars().count() + 1),
};
if options.iter().any(|(k, _): &(Token, Token)| k.text == key) {
return Err(token
.error(ErrorCode::Syntax, format!("Option `{key}` is given twice")));
}
options.push((key_token, value_token));
}
_ => {
if !options.is_empty() {
return Err(token.error(
ErrorCode::Syntax,
format!("Unexpected `{}` after the options", token.text),
));
}
args.push(token);
}
}
}
Ok(Self {
name,
args,
options,
})
}
fn option(&mut self, key: &str) -> Option<Token<'a>> {
let index = self.options.iter().position(|(k, _)| k.text == key)?;
Some(self.options.remove(index).1)
}
fn finish(self, what: &str, allowed: &str) -> Parsed<()> {
if let Some((key, _)) = self.options.first() {
return Err(key.error(
ErrorCode::Syntax,
format!(
"Unknown option `{}` for a {what}; expected {allowed}",
key.text
),
));
}
Ok(())
}
}
impl Token<'_> {
fn end_error(&self, message: String) -> Error {
Error::at(self.end(), ErrorCode::Syntax, message)
}
}
fn check_name(token: Token) -> Parsed<()> {
let valid = token
.text
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-');
if !valid || token.text.is_empty() {
return Err(token.error(
ErrorCode::Syntax,
format!(
"Name `{}` may only use letters, digits, `_` and `-`",
token.text
),
));
}
Ok(())
}
fn check_sprite_name(token: Token) -> Parsed<()> {
let mut offset = 0;
for part in token.text.split('.') {
check_name(Token {
text: part,
pos: token.at(offset),
})?;
offset += part.chars().count() + 1;
}
Ok(())
}
fn parse_base(token: Token) -> Parsed<Color> {
let invalid = || {
token.error(
ErrorCode::InvalidArgument,
format!("A ramp's base is an opaque `#rrggbb`, not `{}`", token.text),
)
};
if token.text.len() != 7 {
return Err(invalid());
}
token.text.parse().map_err(|_| invalid())
}
fn lint_code(name: &str, pos: Position) -> Parsed<LintCode> {
if name.is_empty() {
return Err(Error::at(pos, ErrorCode::Syntax, "Expected a lint code"));
}
name.parse()
.map_err(|e: Error| Error::at(pos, e.code, e.message))
}
const RESERVED_KEYS: &[char] = &[';', '(', ')', ',', '='];
fn size(token: Token, max: u32) -> Parsed<(u32, u32)> {
let invalid = || {
token.error(
ErrorCode::Syntax,
format!("Expected a size such as `16x16`, not `{}`", token.text),
)
};
let (w, h) = token.text.split_once('x').ok_or_else(invalid)?;
let number = |text: &str, offset: usize| -> Parsed<u32> {
if text.is_empty() || !text.bytes().all(|b| b.is_ascii_digit()) {
return Err(invalid());
}
let value: u64 = text.parse().unwrap_or(u64::MAX);
if value == 0 {
return Err(Error::at(
token.at(offset),
ErrorCode::InvalidArgument,
"A size must be at least 1",
));
}
if value > max as u64 {
return Err(Error::at(
token.at(offset),
ErrorCode::LimitExceeded,
format!("Size {value} is more than the limit of {max}"),
));
}
Ok(value as u32)
};
Ok((number(w, 0)?, number(h, w.len() + 1)?))
}
impl<'a> Parser<'a, '_> {
fn definition(&mut self, node: &Node<'a>) -> Parsed<()> {
match node.keyword() {
"palette" => self.palette(node),
"sprite" => self.sprite(node),
"animation" => self.animation(node),
"tileset" => self.tileset(node),
"tilemap" => self.tilemap(node),
"font" => self.font(node),
"text" => self.text_sprite(node),
"states" => self.states(node),
"icons" => self.icons(node),
"theme" => self.theme(node),
"screen" => self.screen(node),
"surface" => self.surface(node),
"background" => self.background(node),
"texture" => self.texture(node),
"lint" => self.lint(node),
"group" => self.group(node),
other => Err(node.line.first().error(
ErrorCode::Syntax,
format!("Unknown directive `{other}`; expected {DIRECTIVES}"),
)),
}
}
fn palette(&mut self, node: &Node<'a>) -> Parsed<()> {
let mut header = Header::new(node, "palette")?;
if let Some(extra) = header.args.first() {
return Err(extra.error(
ErrorCode::Syntax,
format!("Unexpected `{}` after the palette's name", extra.text),
));
}
let from = header.option("from");
if let Some(from) = from {
check_name(from)?;
}
let name = header.name;
let lint = header.option("lint");
header.finish("palette", "`from=` or `lint=`")?;
self.lint_option(Kind::Palette, name, lint)?;
let mut colors: Vec<(char, Color)> = Vec::new();
let mut positions = Vec::new();
let mut ramps: Vec<RampDef<'a>> = Vec::new();
for child in &node.children {
if child.keyword() == "ramp" {
if let Err(e) = self.ramp(child, name, &mut colors, &mut positions, &mut ramps) {
self.errors.push(e);
}
continue;
}
match palette_entry(child) {
Ok((key, color)) => {
let color = color.unwrap_or_else(|e| {
self.errors.push(e);
Color::TRANSPARENT
});
if colors.iter().any(|&(k, _)| k == key) {
self.errors.push(child.line.first().error(
ErrorCode::DuplicateName,
format!("Key `{key}` is defined twice in palette `{}`", name.text),
));
} else {
colors.push((key, color));
positions.push(child.line.first().pos);
}
}
Err(e) => self.errors.push(e),
}
}
self.palettes.push(PaletteDef {
name,
from,
colors,
positions,
ramps,
});
Ok(())
}
fn ramp(
&mut self,
node: &Node<'a>,
palette: Token<'a>,
colors: &mut Vec<(char, Color)>,
positions: &mut Vec<Position>,
ramps: &mut Vec<RampDef<'a>>,
) -> Parsed<()> {
let tokens = &node.line.tokens;
let usage = "write `ramp NAME KEYS`, such as `ramp skin abcd`, dark to light";
let name = *tokens
.get(1)
.ok_or_else(|| tokens[0].end_error(format!("Expected a ramp's name; {usage}")))?;
check_name(name)?;
let keys = *tokens
.get(2)
.ok_or_else(|| name.end_error(format!("Expected the ramp's keys; {usage}")))?;
if keys.text.contains('=') {
return Err(keys.error(
ErrorCode::Syntax,
format!("Expected the ramp's keys before its options; {usage}"),
));
}
let (mut base, mut shift) = (None, None);
for &token in &tokens[3..] {
match token.text.split_once('=') {
Some(("base", text)) if base.is_none() => {
base = Some(Token {
text,
pos: token.at("base=".len()),
});
}
Some(("shift", text)) if shift.is_none() => {
shift = Some(Token {
text,
pos: token.at("shift=".len()),
});
}
_ => {
return Err(token.error(
ErrorCode::Syntax,
format!(
"Unexpected `{}`; a ramp takes `base=#rrggbb` and `shift=DEGREES`",
token.text
),
));
}
}
}
if let Some(child) = node.children.first() {
return Err(child
.line
.first()
.error(ErrorCode::Syntax, "Unexpected indented line under a ramp"));
}
if keys
.text
.chars()
.nth(self.limits.max_palette_colors)
.is_some()
{
return Err(keys.error(
ErrorCode::LimitExceeded,
format!(
"Ramp `{}` has more keys than the limit of {} a palette",
name.text, self.limits.max_palette_colors
),
));
}
let def = RampDef { name, keys };
let written = def.keys();
if written.len() < 2 {
return Err(keys.error(ErrorCode::InvalidArgument, "A ramp has at least two keys"));
}
for (i, &(key, pos)) in written.iter().enumerate() {
if key.is_control() || RESERVED_KEYS.contains(&key) {
return Err(Error::at(
pos,
ErrorCode::InvalidArgument,
format!("`{key}` cannot be a palette key"),
));
}
if written[..i].iter().any(|&(k, _)| k == key) {
return Err(Error::at(
pos,
ErrorCode::DuplicateName,
format!("Key `{key}` is twice in ramp `{}`", name.text),
));
}
if let Some(other) = ramps.iter().find(|r| r.keys.text.contains(key)) {
return Err(Error::at(
pos,
ErrorCode::DuplicateName,
format!("Key `{key}` is in ramp `{}` already", other.name.text),
));
}
}
if let Some(first) = ramps.iter().find(|r| r.name.text == name.text) {
return Err(duplicate(name, "Ramp", first.name.pos));
}
if let Some(shift) = shift {
if base.is_none() {
return Err(Error::at(
shift.pos,
ErrorCode::Syntax,
"`shift=` turns the hues of a ramp made from `base=`; give a base",
));
}
}
if let Some(base) = base {
let color = parse_base(base)?;
let shift = match shift {
Some(t) => t
.text
.parse::<u32>()
.ok()
.filter(|s| *s <= 60 && t.text.bytes().all(|b| b.is_ascii_digit()))
.ok_or_else(|| {
t.error(
ErrorCode::InvalidArgument,
format!("A shift is 0 to 60 degrees, not `{}`", t.text),
)
})?,
None => 20,
};
let made = crate::oklab::ramp(color, written.len(), shift);
for (&(key, pos), color) in written.iter().zip(made) {
if colors.iter().any(|&(k, _)| k == key) {
return Err(Error::at(
pos,
ErrorCode::DuplicateName,
format!("Key `{key}` is defined twice in palette `{}`", palette.text),
));
}
colors.push((key, color));
positions.push(pos);
}
}
ramps.push(def);
Ok(())
}
fn lint(&mut self, node: &Node<'a>) -> Parsed<()> {
let tokens = &node.line.tokens;
match tokens.get(1) {
Some(t) if t.text == "allow" => {}
Some(t) => {
return Err(t.error(
ErrorCode::Syntax,
format!("Expected `lint allow CODE...`, not `lint {}`", t.text),
));
}
None => {
return Err(tokens[0].end_error(
"Expected `allow` and the lint codes, such as `lint allow orphan_pixel`"
.to_owned(),
));
}
}
if tokens.len() < 3 {
return Err(tokens[1].end_error(
"Expected the lint codes to allow, such as `lint allow orphan_pixel`".to_owned(),
));
}
if let Some(child) = node.children.first() {
return Err(child
.line
.first()
.error(ErrorCode::Syntax, "Unexpected indented line under `lint`"));
}
let codes = tokens[2..]
.iter()
.map(|t| lint_code(t.text, t.pos))
.collect::<Parsed<Vec<_>>>()?;
self.file_allows.push((tokens[0].pos.file, codes));
Ok(())
}
fn lint_option(&mut self, kind: Kind, name: Token<'a>, value: Option<Token<'a>>) -> Parsed<()> {
let Some(token) = value else { return Ok(()) };
let invalid = || {
token.error(
ErrorCode::Syntax,
format!("Expected `lint=allow(CODE,...)`, not `lint={}`", token.text),
)
};
let inner = token
.text
.strip_prefix("allow(")
.and_then(|t| t.strip_suffix(')'))
.ok_or_else(invalid)?;
let mut codes = Vec::new();
let mut offset = "allow(".len();
for part in inner.split(',') {
let lead = part.chars().take_while(|c| *c == ' ').count();
codes.push(lint_code(part.trim(), token.at(offset + lead))?);
offset += part.chars().count() + 1;
}
self.allows.push((kind, name, codes));
Ok(())
}
fn sources(&self, files: &[String]) -> Sources {
let allow = |kind: Kind, name: Token| -> Vec<LintCode> {
self.allows
.iter()
.filter(|(k, n, _)| *k == kind && n.pos == name.pos)
.flat_map(|(_, _, codes)| codes.iter().copied())
.collect()
};
let mut defs = Vec::new();
for (kind, name) in self
.surfaces
.iter()
.map(|d| (Kind::Surface, d.name))
.chain(self.backgrounds.iter().map(|d| (Kind::Background, d.name)))
{
defs.push(Def {
kind,
name: name.text.into(),
pos: name.pos,
allow: allow(kind, name),
deps: Vec::new(),
detail: Detail::Other,
});
}
for screen in &self.screens {
defs.push(Def {
kind: Kind::Screen,
name: screen.name.text.into(),
pos: screen.name.pos,
allow: allow(Kind::Screen, screen.name),
deps: Vec::new(),
detail: Detail::Other,
});
}
for p in &self.palettes {
defs.push(Def {
kind: Kind::Palette,
name: p.name.text.to_owned(),
pos: p.name.pos,
allow: allow(Kind::Palette, p.name),
deps: p
.from
.iter()
.map(|t| (Kind::Palette, t.text.to_owned()))
.collect(),
detail: Detail::Palette {
ramps: p
.ramps
.iter()
.map(|r| (r.name.text.to_owned(), r.name.pos, r.keys()))
.collect(),
from: p.from.map(|t| t.text.to_owned()),
keys: p
.colors
.iter()
.zip(&p.positions)
.map(|(&(key, _), &pos)| (key, pos))
.collect(),
},
});
}
for s in &self.sprites {
defs.push(Def {
kind: Kind::Sprite,
name: s.name.text.to_owned(),
pos: s.name.pos,
allow: allow(Kind::Sprite, s.name),
deps: s
.dependencies()
.iter()
.map(|t| (Kind::Sprite, t.text.to_owned()))
.collect(),
detail: s.detail(self.layer_counts.get(&s.name.pos)),
});
}
for &(kind, name, ref codes) in &self.allows {
if matches!(kind, Kind::Palette | Kind::Sprite) {
continue;
}
defs.push(Def {
kind,
name: name.text.to_owned(),
pos: name.pos,
allow: codes.clone(),
deps: Vec::new(),
detail: Detail::Other,
});
}
let mut sets = Vec::new();
for (kind, name, members) in self
.animation_sets()
.into_iter()
.chain(self.group_sets())
.chain(self.tileset_sets())
.chain(self.widget_sets())
{
if !defs.iter().any(|d| d.kind == kind && d.pos == name.pos) {
defs.push(Def {
kind,
name: name.text.to_owned(),
pos: name.pos,
allow: Vec::new(),
deps: Vec::new(),
detail: Detail::Other,
});
}
sets.push(crate::lint::Set {
kind,
name: name.text.to_owned(),
members,
});
}
let mut file_allows = vec![Vec::new(); files.len()];
for (file, codes) in &self.file_allows {
if let Some(allowed) = file_allows.get_mut(*file as usize) {
allowed.extend(codes.iter().copied());
}
}
Sources {
files: files.to_vec(),
file_allows,
defs,
palette_uses: self.palette_uses.clone(),
sets,
}
}
fn resolve(&mut self) -> Document {
let palettes = self.resolve_palettes();
let themes = self.resolve_themes(&palettes);
let fonts = self.resolve_fonts();
let sprites = self.resolve_sprites(&palettes, &fonts);
let mut budget = self.limits.max_image_pixels;
let sprites = self.spend(sprites, &mut budget);
let (animations, frame_sprites) = self.resolve_animations(&palettes, &sprites);
let frame_sprites = self.spend(frame_sprites, &mut budget);
let tilesets = self.resolve_tilesets(&palettes, &sprites);
let tilemaps = self.resolve_tilemaps(&tilesets);
let groups = self.resolve_groups(&sprites);
let (states, icon_sets) = self.resolve_widgets(&sprites);
let screens = self.resolve_screens(&palettes, &sprites, &mut budget);
let mut doc = Document {
surfaces: Vec::new(),
backgrounds: Vec::new(),
screens,
theme_bases: palettes.clone(),
themes,
palettes,
sprites,
frame_sprites,
animations,
tilesets,
tilemaps,
fonts,
groups,
states,
icon_sets,
sources: Sources::default(),
};
self.resolve_scenes(&mut doc);
doc
}
fn spend(&mut self, sprites: Vec<(Sprite, Position)>, budget: &mut u64) -> Vec<Sprite> {
let mut kept = Vec::with_capacity(sprites.len());
for (sprite, pos) in sprites {
let pixels = sprite.width as u64 * sprite.height as u64 * sprite.layers.len() as u64;
if pixels > *budget {
self.errors.push(Error::at(
pos,
ErrorCode::LimitExceeded,
format!(
"The sprites have more than the limit of {} pixels in all",
self.limits.max_image_pixels
),
));
*budget = 0;
break;
}
*budget -= pixels;
kept.push(sprite);
}
kept
}
fn resolve_palettes(&mut self) -> Vec<Palette> {
let mut index: HashMap<&str, usize> = HashMap::new();
let mut usable = vec![true; self.palettes.len()];
for (i, def) in self.palettes.iter().enumerate() {
if let Some(&first) = index.get(def.name.text) {
self.errors.push(duplicate(
def.name,
"Palette",
self.palettes[first].name.pos,
));
usable[i] = false;
} else {
index.insert(def.name.text, i);
}
}
let mut resolved: Vec<Option<Palette>> = vec![None; self.palettes.len()];
let mut depth = vec![0usize; self.palettes.len()];
for start in 0..self.palettes.len() {
if !usable[start] || resolved[start].is_some() {
continue;
}
let mut chain = vec![start];
let mut failed = false;
loop {
let current = &self.palettes[*chain.last().unwrap_or(&start)];
let Some(from) = current.from else { break };
let Some(&next) = index.get(from.text) else {
self.errors.push(from.error(
ErrorCode::UnknownName,
format!("Unknown palette `{}`", from.text),
));
failed = true;
break;
};
if chain.contains(&next) {
self.errors.push(from.error(
ErrorCode::Cycle,
format!(
"Palette `{}` is copied from itself through `from=`",
current.name.text
),
));
failed = true;
break;
}
if resolved[next].is_some() || !usable[next] {
chain.push(next);
break;
}
chain.push(next);
}
let last = chain[chain.len() - 1];
if failed || (!usable[last] && resolved[last].is_none()) {
for &i in &chain {
usable[i] = false;
}
continue;
}
let mut base = resolved[last].clone();
let unresolved = if base.is_some() {
chain.len() - 1
} else {
chain.len()
};
let base_level = if base.is_some() { depth[last] + 1 } else { 0 };
for (n, &i) in chain[..unresolved].iter().enumerate().rev() {
let def = &self.palettes[i];
let level = base_level + (unresolved - 1 - n);
if level > self.limits.max_nesting {
let from = def.from.unwrap_or(def.name);
self.errors.push(from.error(
ErrorCode::LimitExceeded,
format!(
"Palettes copied from palettes more than the limit of {} deep",
self.limits.max_nesting
),
));
for &j in &chain[..=n] {
usable[j] = false;
}
break;
}
let (mut colors, mut ramps) =
base.take().map(|p| (p.colors, p.ramps)).unwrap_or_default();
for &(key, color) in &def.colors {
match colors.iter_mut().find(|(k, _)| *k == key) {
Some(entry) => entry.1 = color,
None => colors.push((key, color)),
}
}
let replaced: Vec<&str> = def.ramps.iter().map(|r| r.name.text).collect();
for ramp in &def.ramps {
let keys = ramp.keys();
let shared = keys.iter().find_map(|&(key, pos)| {
ramps
.iter()
.filter(|r| !replaced.contains(&r.name.as_str()))
.find(|r| r.keys.contains(&key))
.map(|r| (key, pos, r.name.clone()))
});
if let Some((key, pos, other)) = shared {
self.errors.push(Error::at(
pos,
ErrorCode::DuplicateName,
format!(
"Key `{key}` is in ramp `{other}` already, which palette `{}` \
copies",
def.name.text
),
));
continue;
}
let missing = keys
.iter()
.find(|(k, _)| !colors.iter().any(|(c, _)| c == k));
if let Some(&(key, pos)) = missing {
self.errors.push(Error::at(
pos,
ErrorCode::UnknownName,
format!(
"Key `{key}` of ramp `{}` is not in palette `{}`",
ramp.name.text, def.name.text
),
));
continue;
}
let made = crate::Ramp {
name: ramp.name.text.to_owned(),
keys: keys.iter().map(|&(k, _)| k).collect(),
};
match ramps.iter_mut().find(|r| r.name == made.name) {
Some(old) => *old = made,
None => ramps.push(made),
}
}
if colors.len() > self.limits.max_palette_colors {
self.errors.push(def.name.error(
ErrorCode::LimitExceeded,
format!(
"Palette `{}` has {} keys, more than the limit of {}",
def.name.text,
colors.len(),
self.limits.max_palette_colors
),
));
for &j in &chain[..=n] {
usable[j] = false;
}
break;
}
let palette = Palette {
name: def.name.text.to_owned(),
colors,
ramps,
};
resolved[i] = Some(palette.clone());
depth[i] = level;
base = Some(palette);
}
}
resolved.into_iter().flatten().collect()
}
}
fn duplicate(name: Token, what: &str, first: Position) -> Error {
name.error(
ErrorCode::DuplicateName,
format!(
"{what} `{}` is already defined on line {}",
name.text, first.line
),
)
}
fn palette_entry(node: &Node) -> Parsed<(char, Parsed<Color>)> {
let tokens = &node.line.tokens;
let key_token = tokens[0];
if let Some(child) = node.children.first() {
return Err(child.line.first().error(
ErrorCode::Syntax,
"Unexpected indented line under a palette color",
));
}
let mut chars = key_token.text.chars();
let key = chars.next().unwrap_or(' ');
if chars.next().is_some() {
return Err(key_token.error(
ErrorCode::Syntax,
format!(
"A palette key is one character, not `{}`; write `KEY COLOR`",
key_token.text
),
));
}
if key.is_control() || RESERVED_KEYS.contains(&key) {
return Err(key_token.error(
ErrorCode::InvalidArgument,
format!("`{key}` cannot be a palette key"),
));
}
let color_token = *tokens
.get(1)
.ok_or_else(|| key_token.end_error(format!("Expected a color after key `{key}`")))?;
if let Some(extra) = tokens.get(2) {
return Err(extra.error(
ErrorCode::Syntax,
format!("Unexpected `{}` after the color", extra.text),
));
}
let color = color_token
.text
.parse()
.map_err(|e: Error| color_token.error(e.code, e.message));
Ok((key, color))
}