hugr_model/v0/mod.rs
1//! Version 0.
2//!
3//! This module defines representations of the hugr IR as plain data, designed
4//! to be as independent of implementation details of the compiler as feasible.
5//! It can be used by the core compiler, alternative implementations or tooling
6//! that does not need the power/complexity of the full compiler. We provide the
7//! following in-memory representations:
8//!
9//! - [Table]: Efficient intermediate data structure to facilitate conversions.
10//! - [AST]: Abstract syntax tree that uses direct references rather than table indices.
11//!
12//! The table and AST format are interconvertible and can be serialised to
13//! a binary and text format, respectively:
14//!
15//! - [Binary]: Binary serialisation format optimised for performance and size.
16//! - [Text]: Human readable s-expression based text format.
17//!
18//! # Logical Format
19//!
20//! The hugr IR is a hierarchical graph data structure. __Nodes__ represent both
21//! __instructions__ that manipulate runtime values and __symbols__ which
22//! represent named language objects. Instructions have __input__ and __output__ ports
23//! and runtime values flow between ports when they are connected by a __link__.
24//!
25//! Nodes are organised into __regions__ and do not have any explicit ordering
26//! between them; any schedule that respects the data dependencies between nodes
27//! is valid. Previous designs included order-edges that could be added between nodes
28//! to further constrain the ordering; as long as this system is still used, order hint
29//! metadata can be used to encode the additional ordering constraints.
30//!
31//! Regions come in three different kinds. __Module regions__ form the
32//! top level of a module and can only contain symbols. __Dataflow regions__
33//! describe how data flows from the region's __source__ ports to the region's
34//! __target__ ports and are restricted to contain instructions. __Controlflow regions__
35//! are control flow graphs containing dataflow __blocks__, with control flow originating
36//! from the region's source ports and ending in the region's target ports.
37//!
38//! __Terms__ form a meta language that is used to describe types, parameters and metadata that
39//! are known statically. To allow types to be parameterized by statically known values, types
40//! and static values are treated uniformly as terms, enabling a restricted form of dependent typing.
41//! Terms are extensible declaratively via __constructors__. __Constraints__ can be used to express
42//! more complex validation rules.
43//!
44//! # Remaining Mismatch with `hugr-core`
45//!
46//! This data model was designed to encode as much of `hugr-core` as possible while also
47//! filling in conceptual gaps and providing a forward-compatible foundation for future
48//! development. However, there are still some mismatches with `hugr-core` that are not
49//! addressed by conversions in import/export:
50//!
51//! - Some terms can not yet be represented in `hugr-core` although they should be.
52//! Importing such terms will result in an error that declares these terms as currently
53//! unsupported.
54//! - In particular `hugr-core` (as of `v0.22.3`) only allows to define custom runtime types but
55//! can not represent custom term constructors for static types. Implementing support for
56//! static `Term`s in `hugr-core` will allow to use the term system for extensible metadata
57//! and constants. Once that is implemented, the hugr IR will have a unified extension mechanism.
58//! - `hugr-core` uses a JSON encoding for metadata which is supported by `hugr-model` via the
59//! `compat.meta_json` metadata constructor. `hugr-model` further allows to declare custom
60//! metadata constructors so that metadata can be composed of terms. These are currently not
61//! supported in `hugr-core` beyond a few exceptions which are hard-coded into the import/export
62//! process.
63//! - `hugr-core` uses JSON encoded constants. Similarly to metadata, `hugr-model` provides a
64//! compatibility mechanism via the `compat.const_json` constant constructor but also allows
65//! to declare custom constant constructors. Import/export has hard-coded support for a small
66//! fixed set of these for now.
67//! - In `hugr-core` a constant is a `Value`, included in the hugr graph via a `Const` node.
68//! A `LoadConst` node connects to the `Const` node via a static edge. `Value`s are separate
69//! from `Term`s in `hugr-core` and can not refer to local variables or to function nodes.
70//! In `hugr-model` the `Const` node is not needed: The `core.load_const` operation takes
71//! the constant's description as a term argument. This enables `hugr-model` constants to
72//! depend on local variables and to refer to functions in the module (removing the need
73//! for a separate `LoadFunc` operation).
74//! - `Value`s in `hugr-core` have a single representation for every constant. The encoding
75//! of constants as terms in `hugr-model` can use different constructors for the same type
76//! of constant value. This can be useful for large constants by enabling efficient encodings.
77//! For example, a constant array of integers could have a constructor taking a byte string
78//! that consists of the integer values, which is significantly more economical than a generic
79//! representation of arrays that has a term for every element.
80//! - The model does not have types with a copy bound as `hugr-core` does, and instead uses
81//! a more general form of type constraints ([#1556]). Similarly, the model does not have
82//! bounded naturals. We perform a conversion for compatibility where possible, but this does
83//! not fully cover all potential cases of bounds.
84//! - `hugr-model` has support for passing closed regions as static parameters to operations,
85//! which allows for higher-order operations that require their function arguments to be
86//! statically known. We currently do not yet support converting this to `hugr-core`.
87//! - In a model module, ports are connected when they share the same link. This differs from
88//! the type of port connectivity in the graph data structure used by `hugr-core`. However,
89//! `hugr-core` restricts connectivity so that in any group of connected ports there is at
90//! most one output port (for dataflow) or at most one input port (for control flow). In
91//! these cases, there is no mismatch.
92//! - `hugr-core` only allows to define type aliases, but not aliases for other terms. The
93//! alias system is under-developed in both `hugr-core` and `hugr-model` and will need some
94//! considerable design and implementation work (or to be removed if deemed unnecessary).
95//! See [#2558].
96//!
97//! [#1556]: https://github.com/CQCL/hugr/discussions/1556
98//! [#2558]: https://github.com/CQCL/hugr/issues/2558
99//! [Text]: crate::v0::ast
100//! [Binary]: crate::v0::binary
101//! [Table]: crate::v0::table
102//! [AST]: crate::v0::ast
103use ordered_float::OrderedFloat;
104#[cfg(feature = "pyo3")]
105use pyo3::PyTypeInfo as _;
106#[cfg(feature = "pyo3")]
107use pyo3::types::PyAnyMethods as _;
108use smol_str::SmolStr;
109use std::sync::Arc;
110use table::LinkIndex;
111
112/// Describes how a function or symbol should be acted upon by a linker
113#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
114pub enum Visibility {
115 /// The linker should ignore this function or symbol
116 Private,
117 /// The linker should act upon this function or symbol
118 Public,
119}
120
121/// Core function types.
122///
123/// - **Parameter:** `?inputs : (core.list core.type)`
124/// - **Parameter:** `?outputs : (core.list core.type)`
125/// - **Result:** `core.type`
126pub const CORE_FN: &str = "core.fn";
127
128/// The type of runtime types.
129///
130/// Runtime types are the types of values that can flow between nodes at runtime.
131///
132/// - **Result:** `?type : core.static`
133pub const CORE_TYPE: &str = "core.type";
134
135/// The type of static types.
136///
137/// Static types are the types of statically known parameters.
138///
139/// This is the only term that is its own type.
140///
141/// - **Result:** `?type : core.static`
142pub const CORE_STATIC: &str = "core.static";
143
144/// The type of constraints.
145///
146/// - **Result:** `?type : core.static`
147pub const CORE_CONSTRAINT: &str = "core.constraint";
148
149/// The constraint for non-linear runtime data.
150///
151/// Runtime values are copied implicitly by connecting an output port to more
152/// than one input port. Similarly runtime values can be deleted implicitly when
153/// an output port is not connected to any input port. In either of these cases
154/// the type of the runtime value must satisfy this constraint.
155///
156/// - **Parameter:** `?type : core.type`
157/// - **Result:** `core.constraint`
158pub const CORE_NON_LINEAR: &str = "core.nonlinear";
159
160/// The type of metadata.
161///
162/// - **Result:** `?type : core.static`
163pub const CORE_META: &str = "core.meta";
164
165/// Runtime algebraic data types.
166///
167/// Algebraic data types are sums of products of other runtime types.
168///
169/// - **Parameter:** `?variants : (core.list (core.list core.type))`
170/// - **Result:** `core.type`
171pub const CORE_ADT: &str = "core.adt";
172
173/// Type of string literals.
174///
175/// - **Result:** `core.static`
176pub const CORE_STR_TYPE: &str = "core.str";
177
178/// Type of natural number literals.
179///
180/// - **Result:** `core.static`
181pub const CORE_NAT_TYPE: &str = "core.nat";
182
183/// Type of bytes literals.
184///
185/// - **Result:** `core.static`
186pub const CORE_BYTES_TYPE: &str = "core.bytes";
187
188/// Type of float literals.
189///
190/// - **Result:** `core.static`
191pub const CORE_FLOAT_TYPE: &str = "core.float";
192
193/// Type of control flow regions.
194///
195/// - **Parameter:** `?inputs : (core.list (core.list core.type))`
196/// - **Parameter:** `?outputs : (core.list (core.list core.type))`
197/// - **Result:** `core.type`
198pub const CORE_CTRL: &str = "core.ctrl";
199
200/// The type for runtime constants.
201///
202/// - **Parameter:** `?type : core.type`
203/// - **Result:** `core.static`
204pub const CORE_CONST: &str = "core.const";
205
206/// Constants for runtime algebraic data types.
207///
208/// - **Parameter:** `?variants : (core.list core.type)`
209/// - **Parameter:** `?types : (core.list core.static)`
210/// - **Parameter:** `?tag : core.nat`
211/// - **Parameter:** `?values : (core.tuple ?types)`
212/// - **Result:** `(core.const (core.adt ?variants))`
213pub const CORE_CONST_ADT: &str = "core.const.adt";
214
215/// The type for lists of static data.
216///
217/// Lists are finite sequences such that all elements have the same type.
218/// For heterogeneous sequences, see [`CORE_TUPLE_TYPE`].
219///
220/// - **Parameter:** `?type : core.static`
221/// - **Result:** `core.static`
222pub const CORE_LIST_TYPE: &str = "core.list";
223
224/// The type for tuples of static data.
225///
226/// Tuples are finite sequences that allow elements to have different types.
227/// For homogeneous sequences, see [`CORE_LIST_TYPE`].
228///
229/// - **Parameter:** `?types : (core.list core.static)`
230/// - **Result:** `core.static`
231pub const CORE_TUPLE_TYPE: &str = "core.tuple";
232
233/// Operation to call a statically known function.
234///
235/// - **Parameter:** `?inputs : (core.list core.type)`
236/// - **Parameter:** `?outputs : (core.list core.type)`
237/// - **Parameter:** `?func : (core.const (core.fn ?inputs ?outputs))`
238/// - **Result:** `(core.fn ?inputs ?outputs ?ext)`
239pub const CORE_CALL: &str = "core.call";
240
241/// Operation to call a functiion known at runtime.
242///
243/// - **Parameter:** `?inputs : (core.list core.type)`
244/// - **Parameter:** `?outputs : (core.list core.type)`
245/// - **Result:** `(core.fn [(core.fn ?inputs ?outputs) ?inputs ...] ?outputs)`
246pub const CORE_CALL_INDIRECT: &str = "core.call_indirect";
247
248/// Operation to load a constant value.
249///
250/// - **Parameter:** `?type : core.type`
251/// - **Parameter:** `?value : (core.const ?type)`
252/// - **Result:** `(core.fn [] [?type])`
253pub const CORE_LOAD_CONST: &str = "core.load_const";
254
255/// Operation to create a value of an algebraic data type.
256///
257/// - **Parameter:** `?variants : (core.list (core.list core.type))`
258/// - **Parameter:** `?types : (core.list core.type)`
259/// - **Parameter:** `?tag : core.nat`
260/// - **Result:** `(core.fn ?types [(core.adt ?variants)])`
261pub const CORE_MAKE_ADT: &str = "core.make_adt";
262
263/// Constructor for documentation metadata.
264///
265/// - **Parameter:** `?description : core.str`
266/// - **Result:** `core.meta`
267pub const CORE_META_DESCRIPTION: &str = "core.meta.description";
268
269/// Metadata to tag a node or region as the entrypoint of a module.
270///
271/// - **Result:** `core.meta`
272pub const CORE_ENTRYPOINT: &str = "core.entrypoint";
273
274/// Constructor for JSON encoded metadata.
275///
276/// This is included in the model to allow for compatibility with `hugr-core`.
277/// The intention is to deprecate this in the future in favor of metadata
278/// expressed with custom constructors.
279///
280/// - **Parameter:** `?name : core.str`
281/// - **Parameter:** `?json : core.str`
282/// - **Result:** `core.meta`
283pub const COMPAT_META_JSON: &str = "compat.meta_json";
284
285/// Constructor for JSON encoded constants.
286///
287/// This is included in the model to allow for compatibility with `hugr-core`.
288/// The intention is to deprecate this in the future in favor of constants
289/// expressed with custom constructors.
290///
291/// - **Parameter:** `?type : core.type`
292/// - **Parameter:** `?json : core.str`
293/// - **Result:** `(core.const ?type)`
294pub const COMPAT_CONST_JSON: &str = "compat.const_json";
295
296/// Metadata constructor for order hint keys.
297///
298/// Nodes in a dataflow region can be annotated with a key. Each node may have
299/// at most one key and the key must be unique among all nodes in the same
300/// dataflow region. The parent dataflow graph can then use the
301/// `order_hint.order` metadata to imply a desired ordering relation, referring
302/// to the nodes by their key.
303///
304/// - **Parameter:** `?key : core.nat`
305/// - **Result:** `core.meta`
306pub const ORDER_HINT_KEY: &str = "core.order_hint.key";
307
308/// Metadata constructor for order hint keys on input nodes.
309///
310/// When the sources of a dataflow region are represented by an input operation
311/// within the region, this metadata can be attached the region to give the
312/// input node an order hint key.
313///
314/// - **Parameter:** `?key : core.nat`
315/// - **Result:** `core.meta`
316pub const ORDER_HINT_INPUT_KEY: &str = "core.order_hint.input_key";
317
318/// Metadata constructor for order hint keys on output nodes.
319///
320/// When the targets of a dataflow region are represented by an output operation
321/// within the region, this metadata can be attached the region to give the
322/// output node an order hint key.
323///
324/// - **Parameter:** `?key : core.nat`
325/// - **Result:** `core.meta`
326pub const ORDER_HINT_OUTPUT_KEY: &str = "core.order_hint.output_key";
327
328/// Metadata constructor for order hints.
329///
330/// When this metadata is attached to a dataflow region, it can indicate a
331/// preferred ordering relation between child nodes. Code generation must take
332/// this into account when deciding on an execution order. The child nodes are
333/// identified by a key, using the `order_hint.key` metadata.
334///
335/// The graph consisting of both value dependencies between nodes and order
336/// hints must be directed acyclic.
337///
338/// - **Parameter:** `?before : core.nat`
339/// - **Parameter:** `?after : core.nat`
340/// - **Result:** `core.meta`
341pub const ORDER_HINT_ORDER: &str = "core.order_hint.order";
342
343/// Metadata constructor for symbol titles.
344///
345/// The names of functions in `hugr-core` are currently not used for symbol
346/// resolution, but rather serve as a short description of the function.
347/// As such, there is no requirement for uniqueness or formatting.
348/// This metadata can be used to preserve that name when serializing through
349/// `hugr-model`.
350///
351/// - **Parameter:** `?title: core.str`
352/// - **Result:** `core.meta`
353pub const CORE_TITLE: &str = "core.title";
354
355pub mod ast;
356pub mod binary;
357pub mod scope;
358pub mod table;
359
360pub use bumpalo;
361
362/// Type to indicate whether scopes are open or closed.
363#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
364pub enum ScopeClosure {
365 /// A scope that is open and therefore not isolated from its parent scope.
366 #[default]
367 Open,
368 /// A scope that is closed and therefore isolated from its parent scope.
369 Closed,
370}
371
372#[cfg(feature = "pyo3")]
373impl<'py> pyo3::FromPyObject<'py> for ScopeClosure {
374 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
375 let value: usize = ob.getattr("value")?.extract()?;
376 match value {
377 0 => Ok(Self::Open),
378 1 => Ok(Self::Closed),
379 _ => Err(pyo3::exceptions::PyTypeError::new_err(
380 "Invalid ScopeClosure.",
381 )),
382 }
383 }
384}
385
386#[cfg(feature = "pyo3")]
387impl<'py> pyo3::IntoPyObject<'py> for ScopeClosure {
388 type Target = pyo3::PyAny;
389 type Output = pyo3::Bound<'py, Self::Target>;
390 type Error = pyo3::PyErr;
391
392 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
393 let py_module = py.import("hugr.model")?;
394 let py_class = py_module.getattr("ScopeClosure")?;
395
396 match self {
397 ScopeClosure::Open => py_class.getattr("OPEN"),
398 ScopeClosure::Closed => py_class.getattr("CLOSED"),
399 }
400 }
401}
402
403/// The kind of a region.
404#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
405pub enum RegionKind {
406 /// Data flow region.
407 #[default]
408 DataFlow = 0,
409 /// Control flow region.
410 ControlFlow = 1,
411 /// Module region.
412 Module = 2,
413}
414
415#[cfg(feature = "pyo3")]
416impl<'py> pyo3::FromPyObject<'py> for RegionKind {
417 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
418 let value: usize = ob.getattr("value")?.extract()?;
419 match value {
420 0 => Ok(Self::DataFlow),
421 1 => Ok(Self::ControlFlow),
422 2 => Ok(Self::Module),
423 _ => Err(pyo3::exceptions::PyTypeError::new_err(
424 "Invalid RegionKind.",
425 )),
426 }
427 }
428}
429
430#[cfg(feature = "pyo3")]
431impl<'py> pyo3::IntoPyObject<'py> for RegionKind {
432 type Target = pyo3::PyAny;
433 type Output = pyo3::Bound<'py, Self::Target>;
434 type Error = pyo3::PyErr;
435
436 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
437 let py_module = py.import("hugr.model")?;
438 let py_class = py_module.getattr("RegionKind")?;
439
440 match self {
441 RegionKind::DataFlow => py_class.getattr("DATA_FLOW"),
442 RegionKind::ControlFlow => py_class.getattr("CONTROL_FLOW"),
443 RegionKind::Module => py_class.getattr("MODULE"),
444 }
445 }
446}
447
448/// The name of a variable.
449#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
450pub struct VarName(SmolStr);
451
452impl VarName {
453 /// Create a new variable name.
454 pub fn new(name: impl Into<SmolStr>) -> Self {
455 Self(name.into())
456 }
457}
458
459impl AsRef<str> for VarName {
460 fn as_ref(&self) -> &str {
461 self.0.as_ref()
462 }
463}
464
465#[cfg(feature = "pyo3")]
466impl<'py> pyo3::FromPyObject<'py> for VarName {
467 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
468 let name: String = ob.extract()?;
469 Ok(Self::new(name))
470 }
471}
472
473#[cfg(feature = "pyo3")]
474impl<'py> pyo3::IntoPyObject<'py> for &VarName {
475 type Target = pyo3::types::PyString;
476 type Output = pyo3::Bound<'py, Self::Target>;
477 type Error = pyo3::PyErr;
478
479 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
480 Ok(self.as_ref().into_pyobject(py)?)
481 }
482}
483
484/// The name of a symbol.
485#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
486pub struct SymbolName(SmolStr);
487
488impl SymbolName {
489 /// Create a new symbol name.
490 pub fn new(name: impl Into<SmolStr>) -> Self {
491 Self(name.into())
492 }
493}
494
495impl AsRef<str> for SymbolName {
496 fn as_ref(&self) -> &str {
497 self.0.as_ref()
498 }
499}
500
501#[cfg(feature = "pyo3")]
502impl<'py> pyo3::FromPyObject<'py> for SymbolName {
503 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
504 let name: String = ob.extract()?;
505 Ok(Self::new(name))
506 }
507}
508
509/// The name of a link.
510#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
511pub struct LinkName(SmolStr);
512
513impl LinkName {
514 /// Create a new link name.
515 pub fn new(name: impl Into<SmolStr>) -> Self {
516 Self(name.into())
517 }
518
519 /// Create a new link name from a link index.
520 #[must_use]
521 pub fn new_index(index: LinkIndex) -> Self {
522 // TODO: Should named and numbered links have different namespaces?
523 Self(format!("{index}").into())
524 }
525}
526
527impl AsRef<str> for LinkName {
528 fn as_ref(&self) -> &str {
529 self.0.as_ref()
530 }
531}
532
533#[cfg(feature = "pyo3")]
534impl<'py> pyo3::FromPyObject<'py> for LinkName {
535 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
536 let name: String = ob.extract()?;
537 Ok(Self::new(name))
538 }
539}
540
541#[cfg(feature = "pyo3")]
542impl<'py> pyo3::IntoPyObject<'py> for &LinkName {
543 type Target = pyo3::types::PyString;
544 type Output = pyo3::Bound<'py, Self::Target>;
545 type Error = pyo3::PyErr;
546
547 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
548 Ok(self.as_ref().into_pyobject(py)?)
549 }
550}
551
552/// A static literal value.
553///
554/// Literal values may be large since they can include strings and byte
555/// sequences of arbitrary length. To enable cheap cloning and sharing,
556/// strings and byte sequences use reference counting.
557#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
558pub enum Literal {
559 /// String literal.
560 Str(SmolStr),
561 /// Natural number literal (unsigned 64 bit).
562 Nat(u64),
563 /// Byte sequence literal.
564 Bytes(Arc<[u8]>),
565 /// Floating point literal
566 Float(OrderedFloat<f64>),
567}
568
569#[cfg(feature = "pyo3")]
570impl<'py> pyo3::FromPyObject<'py> for Literal {
571 fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
572 if pyo3::types::PyString::is_type_of(ob) {
573 let value: String = ob.extract()?;
574 Ok(Literal::Str(value.into()))
575 } else if pyo3::types::PyInt::is_type_of(ob) {
576 let value: u64 = ob.extract()?;
577 Ok(Literal::Nat(value))
578 } else if pyo3::types::PyFloat::is_type_of(ob) {
579 let value: f64 = ob.extract()?;
580 Ok(Literal::Float(value.into()))
581 } else if pyo3::types::PyBytes::is_type_of(ob) {
582 let value: Vec<u8> = ob.extract()?;
583 Ok(Literal::Bytes(value.into()))
584 } else {
585 Err(pyo3::exceptions::PyTypeError::new_err(
586 "Invalid literal value.",
587 ))
588 }
589 }
590}
591
592#[cfg(feature = "pyo3")]
593impl<'py> pyo3::IntoPyObject<'py> for &Literal {
594 type Target = pyo3::PyAny;
595 type Output = pyo3::Bound<'py, Self::Target>;
596 type Error = pyo3::PyErr;
597
598 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
599 Ok(match self {
600 Literal::Str(s) => s.as_str().into_pyobject(py)?.into_any(),
601 Literal::Nat(n) => n.into_pyobject(py)?.into_any(),
602 Literal::Bytes(b) => pyo3::types::PyBytes::new(py, b)
603 .into_pyobject(py)?
604 .into_any(),
605 Literal::Float(f) => f.0.into_pyobject(py)?.into_any(),
606 })
607 }
608}
609
610#[cfg(test)]
611mod test {
612 use super::*;
613 use proptest::{prelude::*, string::string_regex};
614
615 impl Arbitrary for Literal {
616 type Parameters = ();
617 type Strategy = BoxedStrategy<Self>;
618
619 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
620 prop_oneof![
621 any::<String>().prop_map(|s| Literal::Str(s.into())),
622 any::<u64>().prop_map(Literal::Nat),
623 prop::collection::vec(any::<u8>(), 0..100).prop_map(|v| Literal::Bytes(v.into())),
624 any::<f64>().prop_map(|f| Literal::Float(OrderedFloat(f)))
625 ]
626 .boxed()
627 }
628 }
629
630 impl Arbitrary for SymbolName {
631 type Parameters = ();
632 type Strategy = BoxedStrategy<Self>;
633
634 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
635 string_regex(r"[a-zA-Z\-_][0-9a-zA-Z\-_](\.[a-zA-Z\-_][0-9a-zA-Z\-_])*")
636 .unwrap()
637 .prop_map(Self::new)
638 .boxed()
639 }
640 }
641
642 impl Arbitrary for VarName {
643 type Parameters = ();
644 type Strategy = BoxedStrategy<Self>;
645
646 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
647 string_regex(r"[a-zA-Z\-_][0-9a-zA-Z\-_]")
648 .unwrap()
649 .prop_map(Self::new)
650 .boxed()
651 }
652 }
653
654 proptest! {
655 #[test]
656 fn test_literal_text(lit: Literal) {
657 let text = lit.to_string();
658 let parsed: Literal = text.parse().unwrap();
659 assert_eq!(lit, parsed);
660 }
661 }
662}