Skip to content

Commit 588cb48

Browse files
committed
Add lists as generator parameters
1 parent ed6fcc1 commit 588cb48

23 files changed

Lines changed: 890 additions & 404 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hyperpuzzle_core/src/catalog/builder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl CatalogBuilder {
8080
/// Menus can be populated using [`Self::add_menu_node()`].
8181
///
8282
/// Returns an error if the menu already exists.
83-
pub fn add_menu(&self, menu_id: TypeId, menu_name: String) -> Result<()> {
83+
pub fn add_menu(&self, menu_id: &'static str, menu_name: String) -> Result<()> {
8484
match self.lock_db()?.menus.entry(menu_id) {
8585
hash_map::Entry::Occupied(e) => {
8686
bail!("menu already exists with name {:?}", e.get().name);
@@ -98,15 +98,15 @@ impl CatalogBuilder {
9898
/// exist.
9999
pub fn add_menu_node(
100100
&self,
101-
menu_id: TypeId,
101+
menu_id: &str,
102102
path: String,
103103
content: MenuContent,
104104
priority: i64,
105105
default: bool,
106106
) -> Result<()> {
107107
self.lock_db()?
108108
.menus
109-
.get_mut(&menu_id)
109+
.get_mut(menu_id)
110110
.ok_or_eyre(
111111
"menu must be created using `CatalogBuilder::add_menu()` before it is populated",
112112
)?

crates/hyperpuzzle_core/src/catalog/generator.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,14 @@ impl<T: CatalogObject> Generator<T> {
137137
pub fn default_id(&self) -> CatalogId {
138138
CatalogId {
139139
base: self.meta.id.base.clone(),
140-
args: self
141-
.params
142-
.iter()
143-
.map(|p| p.default.clone().into())
144-
.collect(),
140+
args: self.params.iter().map(|p| p.default.clone()).collect(),
145141
}
146142
}
147143
}
148144

149145
/// Type of [`Generator::generate`].
150146
pub type GenerateFn<T> =
151-
Box<dyn Send + Sync + Fn(BuildCtx, Vec<CatalogArgValue>) -> Result<Redirectable<Arc<T>>>>;
147+
Box<dyn Send + Sync + Fn(BuildCtx, Vec<CatalogIdValue>) -> Result<Redirectable<Arc<T>>>>;
152148

153149
/// Possible ID redirect.
154150
#[derive(Debug, Clone)]

crates/hyperpuzzle_core/src/catalog/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Catalog of puzzles and related objects, along with functionality for loading
22
//! them.
33
4-
use std::any::TypeId;
54
use std::collections::{BTreeSet, HashMap, HashSet, hash_map};
65
use std::fmt;
76
use std::ops::Deref;
@@ -283,8 +282,8 @@ pub struct CatalogData {
283282

284283
/// Puzzle list.
285284
pub puzzle_list: Vec<Arc<CatalogMetadata>>,
286-
/// Menus, indexed by type ID.
287-
pub menus: HashMap<TypeId, Menu>,
285+
/// Menus, indexed by string ID.
286+
pub menus: HashMap<&'static str, Menu>,
288287

289288
/// Alphabetized list of all puzzle definition authors.
290289
pub authors: Vec<String>,

crates/hyperpuzzle_core/src/catalog/params.rs

Lines changed: 96 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,93 +8,151 @@ pub struct GeneratorParam {
88
/// Parameter type.
99
pub ty: GeneratorParamType,
1010
/// Default value.
11-
pub default: GeneratorParamValue,
11+
pub default: CatalogIdValue,
1212
}
1313

1414
impl GeneratorParam {
15-
/// Converts a string to a value for this parameter and returns an error if
16-
/// it is invalid.
17-
pub fn value_from_arg(
15+
/// Converts a catalog ID value into a typed value for this parameter, or
16+
/// returns an error if it is invalid.
17+
pub fn typed_value(
1818
&self,
19-
arg: &CatalogArgValue,
20-
) -> Result<GeneratorParamValue, GeneratorParamError> {
21-
let make_error = || GeneratorParamError {
22-
expected: self.clone(),
23-
got: arg.to_string(),
24-
};
25-
26-
match self.ty {
27-
GeneratorParamType::Int { .. } => Ok(GeneratorParamValue::Int(
28-
arg.to_int().ok_or_else(make_error)?,
29-
)),
30-
GeneratorParamType::Puzzle => Ok(GeneratorParamValue::PuzzleId(arg.to_id())),
19+
arg: CatalogIdValue,
20+
) -> Result<TypedCatalogIdValue, GeneratorParamError> {
21+
match &self.ty {
22+
GeneratorParamType::Bool => arg.to_bool().map(TypedCatalogIdValue::Bool),
23+
GeneratorParamType::Int { .. } => arg.to_int().map(TypedCatalogIdValue::Int),
24+
GeneratorParamType::Puzzle { .. } => arg.into_id().map(TypedCatalogIdValue::Id),
25+
GeneratorParamType::List(inner) => arg
26+
.into_list()
27+
.and_then(|l| l.into_iter().map(|e| inner.typed_value(&e)).try_collect())
28+
.map(TypedCatalogIdValue::List),
3129
}
30+
.map_err(|inner| GeneratorParamError {
31+
param: self.clone(),
32+
inner,
33+
})
3234
}
3335
}
36+
3437
/// Type of a parameter for a puzzle generator.
3538
#[derive(Debug, Clone, PartialEq)]
3639
pub enum GeneratorParamType {
40+
/// Boolean.
41+
Bool,
3742
/// Integer.
3843
Int {
3944
/// Minimum value (inclusive).
4045
min: i64,
4146
/// Maximum value (inclusive).
4247
max: i64,
4348
},
44-
/// Puzzle ID.
45-
Puzzle,
49+
/// Puzzle ID with a menu name.
50+
Puzzle {
51+
/// Puzzle menu ID.
52+
menu: String,
53+
},
54+
/// List of parameters.
55+
///
56+
/// This must be the last parameter.
57+
List(Box<GeneratorParamType>),
4658
}
4759

4860
impl fmt::Display for GeneratorParamType {
4961
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5062
match self {
51-
GeneratorParamType::Int { min, max } => write!(f, "int ({min} to {max})"),
52-
GeneratorParamType::Puzzle => write!(f, "puzzle"),
63+
GeneratorParamType::Bool => write!(f, "true or false"),
64+
GeneratorParamType::Int { min, max } => write!(f, "integer ({min} to {max})"),
65+
GeneratorParamType::Puzzle { menu } => write!(f, "puzzle from {menu:?} menu"),
66+
GeneratorParamType::List(inner) => write!(f, "list of {inner}"),
67+
}
68+
}
69+
}
70+
71+
impl GeneratorParamType {
72+
/// Converts a catalog ID value into a typed value for this parameter, or
73+
/// returns an error if it is invalid.
74+
pub fn typed_value(&self, arg: &CatalogIdValue) -> Result<TypedCatalogIdValue, CatalogIdError> {
75+
match self {
76+
GeneratorParamType::Bool => arg.to_bool().map(TypedCatalogIdValue::Bool),
77+
GeneratorParamType::Int { .. } => arg.to_int().map(TypedCatalogIdValue::Int),
78+
GeneratorParamType::Puzzle { .. } => arg.clone().into_id().map(TypedCatalogIdValue::Id),
79+
GeneratorParamType::List(inner) => arg
80+
.clone()
81+
.into_list()
82+
.and_then(|l| l.into_iter().map(|e| inner.typed_value(&e)).try_collect())
83+
.map(TypedCatalogIdValue::List),
5384
}
5485
}
5586
}
5687

5788
/// Value of a parameter for a puzzle generator.
5889
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59-
pub enum GeneratorParamValue {
90+
pub enum TypedCatalogIdValue {
91+
/// Catalog ID.
92+
Id(CatalogId),
93+
/// Boolean.
94+
Bool(bool),
6095
/// Integer.
6196
Int(i64),
62-
/// Puzzle ID.
63-
PuzzleId(CatalogId),
97+
/// List of values.
98+
List(Vec<TypedCatalogIdValue>),
6499
}
65100

66-
impl fmt::Display for GeneratorParamValue {
101+
impl fmt::Display for TypedCatalogIdValue {
67102
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68103
match self {
69-
GeneratorParamValue::Int(i) => write!(f, "{i}"),
70-
GeneratorParamValue::PuzzleId(id) => write!(f, "{id}"),
104+
TypedCatalogIdValue::Id(id) => write!(f, "{id}"),
105+
TypedCatalogIdValue::Bool(b) => write!(f, "{b}"),
106+
TypedCatalogIdValue::Int(i) => write!(f, "{i}"),
107+
TypedCatalogIdValue::List(l) => {
108+
write!(f, "[")?;
109+
let mut is_first = true;
110+
for elem in l {
111+
if !std::mem::take(&mut is_first) {
112+
write!(f, ",")?;
113+
}
114+
write!(f, "{elem}")?;
115+
}
116+
write!(f, "]")?;
117+
Ok(())
118+
}
71119
}
72120
}
73121
}
74122

75-
impl From<GeneratorParamValue> for CatalogArgValue {
76-
fn from(value: GeneratorParamValue) -> Self {
77-
match value {
78-
GeneratorParamValue::Int(i) => i.into(),
79-
GeneratorParamValue::PuzzleId(id) => id.into(),
123+
impl From<TypedCatalogIdValue> for CatalogIdValue {
124+
fn from(value: TypedCatalogIdValue) -> Self {
125+
value.into_untyped()
126+
}
127+
}
128+
129+
impl TypedCatalogIdValue {
130+
/// Converts a [`TypedCatalogIdValue`] to a [`CatalogIdValue`], which loses
131+
/// the type information.
132+
pub fn into_untyped(self) -> CatalogIdValue {
133+
match self {
134+
Self::Id(id) => id.into(),
135+
Self::Bool(b) => b.into(),
136+
Self::Int(i) => i.into(),
137+
Self::List(l) => l.into_iter().map(|e| e.into()).collect_vec().into(),
80138
}
81139
}
82140
}
83141

84142
/// Error encountered when parsing a generator parameter.
85-
#[derive(Debug, Clone)]
143+
#[derive(Debug)]
86144
pub struct GeneratorParamError {
87145
/// Parameter requirements.
88-
pub expected: GeneratorParam,
89-
/// Value supplied.
90-
pub got: String,
146+
pub param: GeneratorParam,
147+
/// Underlying error.
148+
pub inner: CatalogIdError,
91149
}
92150

93151
impl fmt::Display for GeneratorParamError {
94152
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95-
let Self { expected, got } = self;
96-
let GeneratorParam { name, ty, .. } = expected;
97-
write!(f, "bad value {got:?} for param {name:?} (expected {ty})")
153+
let Self { param, inner } = self;
154+
let GeneratorParam { name, ty, .. } = param;
155+
write!(f, "bad value for param {name:?} (expected {ty}): {inner}")
98156
}
99157
}
100158

crates/hyperpuzzle_core/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ pub use crate::timestamp::Timestamp;
4545
/// Prelude of common imports.
4646
pub mod prelude {
4747
pub use crate::catalog::{
48-
Catalog, CatalogArgValue, CatalogBuilder, CatalogId, CatalogIdParseError, CatalogMetadata,
48+
Catalog, CatalogBuilder, CatalogId, CatalogIdError, CatalogIdValue, CatalogMetadata,
4949
ColorSystemGenerator, GeneratorParam, GeneratorParamError, GeneratorParamType,
50-
GeneratorParamValue, PuzzleGenerator, Redirectable, TwistSystemGenerator,
50+
PuzzleGenerator, Redirectable, TwistSystemGenerator, TypedCatalogIdValue,
5151
};
5252
pub use crate::lint::PuzzleLintOutput;
5353
pub use crate::names::{

crates/hyperpuzzle_impl_symmetric/src/hps/mod.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct HpsSymmetric;
1616

1717
impl HpsSymmetric {
1818
/// ID for the symmetric puzzle [`Menu`].
19-
pub const MENU_ID: TypeId = TypeId::of::<Self>();
19+
pub const MENU_ID: &'static str = "symmetric";
2020
}
2121

2222
impl fmt::Display for HpsSymmetric {
@@ -43,8 +43,6 @@ pub fn define_in(
4343
next_inline: Option<Str>,
4444
section: Option<bool>,
4545
(id, id_span): Option<Str>,
46-
params: Option<Vec<Spanned<Arc<Map>>>>,
47-
build: Option<Arc<FnValue>>,
4846
)]
4947
fn add_menu_entry(ctx: EvalCtx) -> () {
5048
let next = match (next_column, next_inline, section.unwrap_or(false), id) {
@@ -68,13 +66,6 @@ pub fn define_in(
6866
),
6967
};
7068

71-
if params.is_some() {
72-
ctx.warn("unhandled `params`");
73-
}
74-
if build.is_some() {
75-
ctx.warn("unhandled `build`");
76-
}
77-
7869
cat.add_menu_node(
7970
HpsSymmetric::MENU_ID,
8071
path.into(),

crates/hyperpuzzle_impl_symmetric/src/lib.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,35 @@ pub fn add_puzzles_to_catalog(catalog: &hyperpuzzle_core::CatalogBuilder) -> Res
4545
product_tags.insert_named("type/generator", true.into())?;
4646
product_tags.insert_named("algebraic/doctrinaire", true.into())?;
4747

48-
// catalog.add_puzzle_generator(Arc::new(PuzzleGenerator {
49-
// meta: Arc::new(CatalogMetadata {
50-
// id: CatalogId {
51-
// base: "product".into(),
52-
// args: vec![],
53-
// },
54-
// version: Version {
55-
// major: 1,
56-
// minor: 0,
57-
// patch: 0,
58-
// },
59-
// name: "Puzzle Product".into(),
60-
// aliases: vec![],
61-
// tags: product_tags.clone(),
62-
// }),
63-
// params: vec![GeneratorParam],
64-
// generate_meta: todo!(),
65-
// generate: todo!(),
66-
// }));
48+
catalog.add_puzzle_generator(Arc::new(PuzzleGenerator {
49+
meta: Arc::new(CatalogMetadata {
50+
id: CatalogId {
51+
base: "product".into(),
52+
args: vec![],
53+
},
54+
version: Version {
55+
major: 1,
56+
minor: 0,
57+
patch: 0,
58+
},
59+
name: "Puzzle Product".into(),
60+
aliases: vec![],
61+
tags: product_tags.clone(),
62+
}),
63+
params: vec![GeneratorParam {
64+
name: "Factors".to_string(),
65+
ty: GeneratorParamType::List(Box::new(GeneratorParamType::Puzzle {
66+
menu: "symmetric".to_string(),
67+
})),
68+
default: CatalogIdValue::List(vec![
69+
"ft_ngon(5)".parse().unwrap(),
70+
"ft_ngon(5)".parse().unwrap(),
71+
]),
72+
}],
73+
generate_meta: Box::new(|_, _| eyre::bail!("todo")),
74+
generate: Box::new(|_, _| eyre::bail!("todo")),
75+
components: ComponentList::new(),
76+
}));
6777

6878
// catalog.add_puzzle_generator(Arc::new(PuzzleGenerator {
6979
// meta: Arc::new(CatalogMetadata {

crates/hyperpuzzlescript/src/builtins/catalog/color_systems.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub fn define_in(
6666
let tx = tx.clone();
6767
let hps_gen = super::generators::HpsGenerator {
6868
def_span: ctx.caller_span,
69-
id: CatalogId::new(id, []).ok_or("invalid ID").at(id_span)?,
69+
id: CatalogId::new(id, []).at(id_span)?,
7070
id_span,
7171
params: super::generators::params_from_array(params)?,
7272
params_span,

0 commit comments

Comments
 (0)