1use crate::{
2 cli::{
3 call_expr::{Argument, Span},
4 try_parse_bool,
5 },
6 record_data::{
7 field_value::FieldValue,
8 field_value_ref::FieldValueRef,
9 scope_manager::{Atom, ScopeId, ScopeManager, ScopeValue},
10 },
11 typelist,
12 utils::{
13 int_string_conversions::parse_int_with_units_from_bytes,
14 maybe_text::MaybeTextRef, string_store::StringStoreEntry,
15 },
16};
17
18use bstr::ByteSlice;
19use num::Zero;
20use std::{
21 ffi::OsString, marker::PhantomData, os::unix::ffi::OsStringExt,
22 path::PathBuf, sync::Arc,
23};
24use thiserror::Error;
25
26pub trait SettingTypeConverter {
27 type Type;
28 fn convert_to_type_from_maybe_text(
29 value: MaybeTextRef,
30 ) -> Result<Self::Type, SettingConversionError>;
31 fn convert_to_type(
32 value: FieldValueRef,
33 ) -> Result<Self::Type, SettingConversionError>;
34 fn convert_from_type(
35 value: Self::Type,
36 ) -> Result<FieldValue, SettingConversionError>;
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Error)]
40#[error("{message}")]
41pub struct SettingConversionError {
42 pub message: String,
43}
44
45#[derive(Clone, Copy, Default)]
46pub enum BufferingMode {
47 BlockBuffer,
48 LineBuffer,
49 LineBufferStdin,
50 #[default]
51 LineBufferIfTTY,
52 LineBufferStdinIfTTY,
53}
54
55pub const RARTIONAL_DECIMALS_DEFAULT_CUTOFF: u32 = 40;
56
57#[derive(Clone, Copy)]
58pub enum RationalsPrintMode {
59 Cutoff(u32),
60 Raw,
61 Dynamic,
62 }
64
65pub type ChainSettingNames = [StringStoreEntry; chain_settings_list::COUNT];
66
67pub trait ChainSetting: chain_settings_list::TypeList {
68 const NAME: &'static str;
69 const DEFAULT: Self::Type;
70 type Type;
71 type Converter: SettingTypeConverter<Type = Self::Type>;
72
73 fn lookup(
74 sm: &ScopeManager,
75 names: &ChainSettingNames,
76 scope_id: ScopeId,
77 ) -> Option<(Result<Self::Type, SettingConversionError>, Span)> {
78 sm.visit_value(scope_id, names[Self::INDEX], |v| {
79 let atom = v.atom()?;
80 let value = atom.value.read().unwrap();
81 if let FieldValue::Argument(arg) = &*value {
82 Some((
83 Self::Converter::convert_to_type(arg.value.as_ref()),
84 arg.span,
85 ))
86 } else {
87 Some((
88 Self::Converter::convert_to_type(value.as_ref()),
89 Span::Generated,
90 ))
91 }
92 })
93 }
94
95 fn assign_raw(
96 sm: &mut ScopeManager,
97 names: &ChainSettingNames,
98 scope_id: ScopeId,
99 value: FieldValue,
100 ) {
101 match sm.scopes[scope_id].values.entry(names[Self::INDEX]) {
102 std::collections::hash_map::Entry::Occupied(mut e) => {
103 match e.get_mut() {
104 ScopeValue::Atom(v) => {
105 *v.value.write().unwrap() = value;
106 }
107 other => {
108 *other = ScopeValue::Atom(Arc::new(Atom::new(value)));
109 }
110 }
111 }
112 std::collections::hash_map::Entry::Vacant(e) => {
113 e.insert(ScopeValue::Atom(Arc::new(Atom::new(value))));
114 }
115 }
116 }
117
118 fn assign(
119 sm: &mut ScopeManager,
120 chain_names: &ChainSettingNames,
121 scope_id: ScopeId,
122 value: Self::Type,
123 span: Span,
124 ) -> Result<(), SettingConversionError> {
125 let value = FieldValue::Argument(Box::new(Argument {
126 value: Self::Converter::convert_from_type(value)?,
127 span,
128 source_scope: scope_id,
129 meta_info: None,
130 }));
131 Self::assign_raw(sm, chain_names, scope_id, value);
132 Ok(())
133 }
134}
135
136impl SettingConversionError {
137 pub fn new(message: String) -> Self {
138 Self { message }
139 }
140}
141
142impl Default for RationalsPrintMode {
143 fn default() -> Self {
144 RationalsPrintMode::Cutoff(RARTIONAL_DECIMALS_DEFAULT_CUTOFF)
145 }
146}
147
148pub struct SettingConverterUsize<
149 S: ChainSetting,
150 const ALLOW_ZERO: bool = true,
151>(PhantomData<S>);
152
153impl<S: ChainSetting, const ALLOW_ZERO: bool> SettingTypeConverter
154 for SettingConverterUsize<S, ALLOW_ZERO>
155{
156 type Type = usize;
157 fn convert_to_type(
158 v: FieldValueRef,
159 ) -> Result<usize, SettingConversionError> {
160 let FieldValueRef::Int(&value) = v else {
161 return Err(SettingConversionError::new(format!(
162 "value for setting %{} must be an integer",
163 S::NAME
164 )));
165 };
166 if value == 0 && !ALLOW_ZERO {
167 return Err(SettingConversionError::new(format!(
168 "value for setting %{} cannot be zero",
169 S::NAME
170 )));
171 }
172 match usize::try_from(value) {
173 Ok(v) => Ok(v),
174 Err(_) => Err(SettingConversionError::new(format!(
175 "value for setting %{} must be a positive integer below 2^{}",
176 S::NAME,
177 usize::BITS
178 ))),
179 }
180 }
181
182 fn convert_from_type(
183 value: usize,
184 ) -> Result<FieldValue, SettingConversionError> {
185 if value == 0 && !ALLOW_ZERO {
186 return Err(SettingConversionError::new(format!(
187 "value for setting %{} cannot be zero",
188 S::NAME,
189 )));
190 }
191 match i64::try_from(value) {
192 Ok(v) => Ok(FieldValue::Int(v)),
193 Err(_) => Err(SettingConversionError::new(format!(
194 "value for setting %{} must be below 2^{}",
195 S::NAME,
196 usize::BITS
197 ))),
198 }
199 }
200
201 fn convert_to_type_from_maybe_text(
202 value: MaybeTextRef,
203 ) -> Result<Self::Type, SettingConversionError> {
204 let v = parse_int_with_units_from_bytes(value.as_bytes()).map_err(
205 |e| {
206 SettingConversionError::new(format!(
207 "value for setting %{} must be an integer: {e}",
208 S::NAME
209 ))
210 },
211 )?;
212 if v == 0 && !ALLOW_ZERO {
213 return Err(SettingConversionError::new(format!(
214 "value for setting %{} cannot be zero",
215 S::NAME
216 )));
217 }
218 Ok(v)
219 }
220}
221
222pub struct SettingConverterBool<S: ChainSetting>(PhantomData<S>);
223impl<S: ChainSetting> SettingTypeConverter for SettingConverterBool<S> {
224 type Type = bool;
225 fn convert_to_type(
226 value: FieldValueRef,
227 ) -> Result<bool, SettingConversionError> {
228 match value {
229 FieldValueRef::Undefined | FieldValueRef::Null => Ok(false),
230 FieldValueRef::Int(v) => Ok(*v == 0),
231 FieldValueRef::BigInt(v) => Ok(v.is_zero()),
232 FieldValueRef::Float(v) => Ok(*v == 0.0),
233 FieldValueRef::BigRational(v) => Ok(v.is_zero()),
234 FieldValueRef::Text(_) | FieldValueRef::Bytes(_) => {
235 Self::convert_to_type_from_maybe_text(
237 value.as_maybe_text_ref().unwrap(),
238 )
239 }
240 FieldValueRef::Argument(v) => {
241 Self::convert_to_type(v.value.as_ref())
242 }
243 FieldValueRef::Array(_)
244 | FieldValueRef::Object(_)
245 | FieldValueRef::Custom(_)
246 | FieldValueRef::Error(_)
247 | FieldValueRef::OpDecl(_)
248 | FieldValueRef::StreamValueId(_)
249 | FieldValueRef::FieldReference(_)
250 | FieldValueRef::SlicedFieldReference(_) => {
251 Err(SettingConversionError::new(format!(
252 "setting %{} expects a boolean, got type {}",
253 S::NAME,
254 value.repr().kind().to_str()
255 )))
256 }
257 }
258 }
259 fn convert_to_type_from_maybe_text(
260 value: MaybeTextRef,
261 ) -> Result<Self::Type, SettingConversionError> {
262 if let Some(v) = try_parse_bool(value.as_bytes()) {
263 return Ok(v);
264 };
265 Err(SettingConversionError::new(format!(
266 "setting %{} expects a boolean, got '{}'",
267 S::NAME,
268 value.as_bytes().to_str_lossy()
269 )))
270 }
271
272 fn convert_from_type(
273 value: bool,
274 ) -> Result<FieldValue, SettingConversionError> {
275 Ok(FieldValue::Text(
276 if value { "true" } else { "false" }.to_string(),
277 ))
278 }
279}
280
281pub struct SettingConverterBufferingMode<S: ChainSetting>(PhantomData<S>);
282impl<S: ChainSetting> SettingTypeConverter
283 for SettingConverterBufferingMode<S>
284{
285 type Type = BufferingMode;
286
287 fn convert_to_type_from_maybe_text(
288 value: MaybeTextRef,
289 ) -> Result<Self::Type, SettingConversionError> {
290 Self::convert_to_type(FieldValueRef::from(value))
291 }
292
293 fn convert_to_type(
294 value: FieldValueRef,
295 ) -> Result<BufferingMode, SettingConversionError> {
296 let FieldValueRef::Text(value) = value else {
297 return Err(SettingConversionError::new(format!(
298 "invalid line buffering condition for %{}, got type `{}`",
299 S::NAME,
300 value.kind().to_str()
301 )));
302 };
303 match value {
304 "never" => Ok(BufferingMode::BlockBuffer),
305 "always" => Ok(BufferingMode::LineBuffer),
306 "stdin" => Ok(BufferingMode::LineBufferStdin),
307 "tty" => Ok(BufferingMode::LineBufferIfTTY),
308 "stdin-if-tty" => Ok(BufferingMode::LineBufferStdinIfTTY),
309 other => Err(SettingConversionError::new(format!(
310 "invalid line buffering condition for %{}, got '{}'",
311 S::NAME,
312 other,
313 ))),
314 }
315 }
316
317 fn convert_from_type(
318 value: BufferingMode,
319 ) -> Result<FieldValue, SettingConversionError> {
320 let v = match value {
321 BufferingMode::BlockBuffer => "never",
322 BufferingMode::LineBuffer => "always",
323 BufferingMode::LineBufferStdin => "stdin",
324 BufferingMode::LineBufferIfTTY => "tty",
325 BufferingMode::LineBufferStdinIfTTY => "stdin-if-tty",
326 };
327 Ok(FieldValue::Text(v.to_string()))
328 }
329}
330
331pub struct SettingConverterRationalsPrintMode<S: ChainSetting>(PhantomData<S>);
332impl<S: ChainSetting> SettingTypeConverter
333 for SettingConverterRationalsPrintMode<S>
334{
335 type Type = RationalsPrintMode;
336
337 fn convert_to_type_from_maybe_text(
338 value: MaybeTextRef,
339 ) -> Result<Self::Type, SettingConversionError> {
340 Self::convert_to_type(FieldValueRef::from(value))
341 }
342 fn convert_to_type(
343 value: FieldValueRef,
344 ) -> Result<RationalsPrintMode, SettingConversionError> {
345 let FieldValueRef::Text(value) = value else {
346 return Err(SettingConversionError::new(format!(
347 "invalid rational printing variant for %{}, got type `{}`",
348 S::NAME,
349 value.kind().to_str()
350 )));
351 };
352 match value {
353 "raw" => return Ok(RationalsPrintMode::Raw),
354 "dynamic" => return Ok(RationalsPrintMode::Dynamic),
355 "cutoff" => {
356 return Ok(RationalsPrintMode::Cutoff(
357 RARTIONAL_DECIMALS_DEFAULT_CUTOFF,
358 ))
359 }
360 _ => (),
361 }
362 if let Some(amount) = value.strip_prefix("cutoff-") {
363 if let Ok(n) = amount.parse::<u32>() {
364 return Ok(RationalsPrintMode::Cutoff(n));
365 }
366 return Err(SettingConversionError::new(format!(
367 "cutoff amount for %{} must be a valid integer, got '{}'",
368 S::NAME,
369 amount,
370 )));
371 }
372 Err(SettingConversionError::new(format!(
373 "invalid rational printing variant for %{}, got '{}'",
374 S::NAME,
375 value,
376 )))
377 }
378
379 fn convert_from_type(
380 value: RationalsPrintMode,
381 ) -> Result<FieldValue, SettingConversionError> {
382 let v = match value {
383 RationalsPrintMode::Cutoff(v) => {
384 if v == RARTIONAL_DECIMALS_DEFAULT_CUTOFF {
385 "cutoff".to_string()
386 } else {
387 format!("cutoff-{v}")
388 }
389 }
390 RationalsPrintMode::Raw => "raw".to_string(),
391 RationalsPrintMode::Dynamic => "dynamic".to_string(),
392 };
393 Ok(FieldValue::Text(v))
394 }
395}
396
397pub struct SettingConverterPath<S: ChainSetting>(PhantomData<S>);
398impl<S: ChainSetting> SettingTypeConverter for SettingConverterPath<S> {
399 type Type = PathBuf;
400
401 fn convert_to_type_from_maybe_text(
402 value: MaybeTextRef,
403 ) -> Result<Self::Type, SettingConversionError> {
404 Self::convert_to_type(FieldValueRef::from(value))
405 }
406
407 fn convert_to_type(
408 value: FieldValueRef,
409 ) -> Result<PathBuf, SettingConversionError> {
410 match value {
411 FieldValueRef::Text(v) => Ok(PathBuf::from(v)),
412 FieldValueRef::Bytes(v) => {
413 Ok(PathBuf::from(OsString::from_vec(v.to_owned())))
414 }
415 FieldValueRef::Undefined | FieldValueRef::Null |
416 FieldValueRef::Int(_)
417 | FieldValueRef::BigInt(_)
418 | FieldValueRef::Float(_)
419 | FieldValueRef::BigRational(_)
420 | FieldValueRef::Array(_)
421 | FieldValueRef::Object(_)
422 | FieldValueRef::Custom(_)
423 | FieldValueRef::Error(_)
424 | FieldValueRef::OpDecl(_)
425 | FieldValueRef::Argument(_)
426 | FieldValueRef::StreamValueId(_)
427 | FieldValueRef::FieldReference(_)
428 | FieldValueRef::SlicedFieldReference(_) => {
429 Err(SettingConversionError::new(format!(
430 "invalid value for setting %{}, expected string, got type `{}`",
431 S::NAME,
432 value.kind().to_str()
433 )))
434 }
435 }
436 }
437
438 fn convert_from_type(
439 value: PathBuf,
440 ) -> Result<FieldValue, SettingConversionError> {
441 match value.into_os_string().into_string() {
442 Ok(v) => Ok(FieldValue::Text(v)),
443 Err(v) => Ok(FieldValue::Bytes(v.into_encoded_bytes())),
444 }
445 }
446}
447
448pub struct SettingConverterOptional<
449 SC: SettingTypeConverter,
450 const EMPTY_STRING_AS_NONE: bool = false,
451>(PhantomData<SC>);
452
453impl<SC: SettingTypeConverter, const EMPTY_STRING_AS_NONE: bool>
454 SettingTypeConverter
455 for SettingConverterOptional<SC, EMPTY_STRING_AS_NONE>
456{
457 type Type = Option<SC::Type>;
458
459 fn convert_to_type_from_maybe_text(
460 value: MaybeTextRef,
461 ) -> Result<Self::Type, SettingConversionError> {
462 if EMPTY_STRING_AS_NONE && value.is_empty() {
463 return Ok(None);
464 }
465 Ok(Some(SC::convert_to_type_from_maybe_text(value)?))
466 }
467
468 fn convert_to_type(
469 value: FieldValueRef,
470 ) -> Result<Option<SC::Type>, SettingConversionError> {
471 match value {
472 FieldValueRef::Null | FieldValueRef::Undefined => Ok(None),
473 FieldValueRef::Text(v) if v.is_empty() && EMPTY_STRING_AS_NONE => {
474 Ok(None)
475 }
476 FieldValueRef::Bytes(v)
477 if v.is_empty() && EMPTY_STRING_AS_NONE =>
478 {
479 Ok(None)
480 }
481 _ => Ok(Some(SC::convert_to_type(value)?)),
482 }
483 }
484
485 fn convert_from_type(
486 value: Option<SC::Type>,
487 ) -> Result<FieldValue, SettingConversionError> {
488 match value {
489 Some(v) => SC::convert_from_type(v),
490 None => Ok(FieldValue::Null),
491 }
492 }
493}
494
495pub struct SettingBatchSize;
496impl ChainSetting for SettingBatchSize {
497 type Type = usize;
498 const NAME: &'static str = "bs";
499 const DEFAULT: usize = 1024;
500 type Converter = SettingConverterUsize<Self, false>;
501}
502
503pub struct SettingStreamSizeThreshold;
504impl ChainSetting for SettingStreamSizeThreshold {
505 type Type = usize;
506 const NAME: &'static str = "sst";
507 const DEFAULT: usize = 1024;
508 type Converter = SettingConverterUsize<Self, false>;
509}
510
511pub struct SettingStreamBufferSize;
512impl ChainSetting for SettingStreamBufferSize {
513 type Type = usize;
514 const NAME: &'static str = "sbs";
515 const DEFAULT: usize = 8192;
516 type Converter = SettingConverterUsize<Self, false>;
517}
518
519pub struct SettingRationalsPrintMode;
520impl ChainSetting for SettingRationalsPrintMode {
521 type Type = RationalsPrintMode;
522 const NAME: &'static str = "rpm";
523 const DEFAULT: RationalsPrintMode =
524 RationalsPrintMode::Cutoff(RARTIONAL_DECIMALS_DEFAULT_CUTOFF);
525 type Converter = SettingConverterRationalsPrintMode<Self>;
526}
527
528pub struct SettingUseFloatingPointMath;
529impl ChainSetting for SettingUseFloatingPointMath {
530 type Type = bool;
531 const NAME: &'static str = "fpm";
532 const DEFAULT: bool = false;
533 type Converter = SettingConverterBool<Self>;
534}
535
536pub struct SettingBufferingMode;
537impl ChainSetting for SettingBufferingMode {
538 type Type = BufferingMode;
539 const NAME: &'static str = "lb";
540 const DEFAULT: BufferingMode = BufferingMode::LineBufferIfTTY;
541 type Converter = SettingConverterBufferingMode<Self>;
542}
543
544pub struct SettingDebugLog;
545impl ChainSetting for SettingDebugLog {
546 type Type = Option<PathBuf>;
547 const NAME: &'static str = "debug_log";
548 const DEFAULT: Option<PathBuf> = None;
549 type Converter = SettingConverterOptional<SettingConverterPath<Self>>;
550}
551
552pub struct SettingDebugLogNoApply;
553impl ChainSetting for SettingDebugLogNoApply {
554 type Type = bool;
555 const NAME: &'static str = "debug_log_no_apply";
556 const DEFAULT: bool = false;
557 type Converter = SettingConverterBool<Self>;
558}
559
560pub struct SettingDebugLogStepMin;
561impl ChainSetting for SettingDebugLogStepMin {
562 type Type = usize;
563 const NAME: &'static str = "debug_log_step_min";
564 const DEFAULT: usize = 0;
565 type Converter = SettingConverterUsize<Self>;
566}
567
568pub struct SettingDebugBreakOnStep;
569impl ChainSetting for SettingDebugBreakOnStep {
570 type Type = Option<usize>;
571 const NAME: &'static str = "debug_break_on_step";
572 const DEFAULT: Option<usize> = None;
573 type Converter = SettingConverterOptional<SettingConverterUsize<Self>>;
574}
575
576pub struct SettingMaxThreads;
577impl ChainSetting for SettingMaxThreads {
578 type Type = usize;
579 const NAME: &'static str = "j";
580 const DEFAULT: usize = 0;
581 type Converter = SettingConverterUsize<Self, true>;
582}
583
584pub struct SettingActionListCleanupFrequency;
585impl ChainSetting for SettingActionListCleanupFrequency {
586 type Type = usize;
587 const NAME: &'static str = "alcf";
588 const DEFAULT: usize = 64;
589 type Converter = SettingConverterUsize<Self, true>;
590}
591
592typelist! {
593 pub mod chain_settings_list: (ChainSetting) = [
594 SettingBatchSize,
595 SettingStreamSizeThreshold,
596 SettingStreamBufferSize,
597 SettingRationalsPrintMode,
598 SettingUseFloatingPointMath,
599 SettingBufferingMode,
600 SettingDebugLog,
601 SettingDebugLogNoApply,
602 SettingDebugLogStepMin,
603 SettingDebugBreakOnStep,
604 SettingMaxThreads,
605 SettingActionListCleanupFrequency
606 ]{}
607}