Skip to content

Commit

Permalink
feat: Add string type and print function to prelude (#942)
Browse files Browse the repository at this point in the history
Closes #905 .
  • Loading branch information
cqc-alec authored Apr 17, 2024
1 parent d016665 commit b7df2ef
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 12 deletions.
101 changes: 97 additions & 4 deletions hugr/src/extension/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,20 @@ lazy_static! {
TypeDefBound::Explicit(crate::types::TypeBound::Eq),
)
.unwrap();


prelude
.add_type(
prelude.add_type(
STRING_TYPE_NAME,
vec![],
"string".into(),
TypeDefBound::Explicit(crate::types::TypeBound::Eq),
)
.unwrap();
prelude.add_op(
SmolStr::new_inline(PRINT_OP_ID),
"Print the string to standard output".to_string(),
FunctionType::new(type_row![STRING_TYPE], type_row![]),
)
.unwrap();
prelude.add_type(
SmolStr::new_inline("array"),
vec![ TypeParam::max_nat(), TypeBound::Any.into()],
"array".into(),
Expand Down Expand Up @@ -148,6 +158,54 @@ pub fn new_array_op(element_ty: Type, size: u64) -> CustomOp {
.into()
}

/// Name of the string type.
pub const STRING_TYPE_NAME: SmolStr = SmolStr::new_inline("string");

/// Custom type for strings.
pub const STRING_CUSTOM_TYPE: CustomType =
CustomType::new_simple(STRING_TYPE_NAME, PRELUDE_ID, TypeBound::Eq);

/// String type.
pub const STRING_TYPE: Type = Type::new_extension(STRING_CUSTOM_TYPE);

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
/// Structure for holding constant string values.
pub struct ConstString(String);

impl ConstString {
/// Creates a new [`ConstString`].
pub fn new(value: String) -> Self {
Self(value)
}

/// Returns the value of the constant.
pub fn value(&self) -> &str {
&self.0
}
}

#[typetag::serde]
impl CustomConst for ConstString {
fn name(&self) -> SmolStr {
format!("ConstString({:?})", self.0).into()
}

fn equal_consts(&self, other: &dyn CustomConst) -> bool {
crate::ops::constant::downcast_equal_consts(self, other)
}

fn extension_reqs(&self) -> ExtensionSet {
ExtensionSet::singleton(&PRELUDE_ID)
}

fn get_type(&self) -> Type {
STRING_TYPE
}
}

/// Name of the print operation
pub const PRINT_OP_ID: &str = "print";

/// The custom type for Errors.
pub const ERROR_CUSTOM_TYPE: CustomType =
CustomType::new_simple(ERROR_TYPE_NAME, PRELUDE_ID, TypeBound::Eq);
Expand Down Expand Up @@ -239,6 +297,7 @@ mod test {
use crate::{
builder::{DFGBuilder, Dataflow, DataflowHugr},
types::FunctionType,
Hugr, Wire,
};

use super::*;
Expand Down Expand Up @@ -298,4 +357,38 @@ mod test {

b.finish_prelude_hugr_with_outputs([]).unwrap();
}

#[test]
/// Test string type.
fn test_string_type() {
let string_custom_type: CustomType = PRELUDE
.get_type(&STRING_TYPE_NAME)
.unwrap()
.instantiate([])
.unwrap();
let string_type: Type = Type::new_extension(string_custom_type);
assert_eq!(string_type, STRING_TYPE);
let string_const: ConstString = ConstString::new("Lorem ipsum".into());
assert_eq!(string_const.name(), "ConstString(\"Lorem ipsum\")");
assert!(string_const.validate().is_ok());
assert_eq!(
string_const.extension_reqs(),
ExtensionSet::singleton(&PRELUDE_ID)
);
assert!(string_const.equal_consts(&ConstString::new("Lorem ipsum".into())));
assert!(!string_const.equal_consts(&ConstString::new("Lorem ispum".into())));
}

#[test]
/// Test print operation
fn test_print() {
let mut b: DFGBuilder<Hugr> = DFGBuilder::new(FunctionType::new(vec![], vec![])).unwrap();
let greeting: ConstString = ConstString::new("Hello, world!".into());
let greeting_out: Wire = b.add_load_const(greeting);
let print_op = PRELUDE
.instantiate_extension_op(PRINT_OP_ID, [], &PRELUDE_REGISTRY)
.unwrap();
b.add_dataflow_op(print_op, [greeting_out]).unwrap();
b.finish_prelude_hugr_with_outputs([]).unwrap();
}
}
5 changes: 4 additions & 1 deletion hugr/src/std_extensions/arithmetic/float_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use strum_macros::{EnumIter, EnumString, IntoStaticStr};
use super::float_types::FLOAT64_TYPE;
use crate::{
extension::{
prelude::BOOL_T,
prelude::{BOOL_T, STRING_TYPE},
simple_op::{MakeOpDef, MakeRegisteredOp, OpLoadError},
ExtensionId, ExtensionRegistry, ExtensionSet, OpDef, SignatureFunc, PRELUDE,
},
Expand Down Expand Up @@ -38,6 +38,7 @@ pub enum FloatOps {
fdiv,
ffloor,
fceil,
ftostring,
}

impl MakeOpDef for FloatOps {
Expand All @@ -56,6 +57,7 @@ impl MakeOpDef for FloatOps {
FunctionType::new(type_row![FLOAT64_TYPE; 2], type_row![FLOAT64_TYPE])
}
fneg | fabs | ffloor | fceil => FunctionType::new_endo(type_row![FLOAT64_TYPE]),
ftostring => FunctionType::new(type_row![FLOAT64_TYPE], STRING_TYPE),
}
.into()
}
Expand All @@ -79,6 +81,7 @@ impl MakeOpDef for FloatOps {
fdiv => "division",
ffloor => "floor",
fceil => "ceiling",
ftostring => "string representation",
}
.to_string()
}
Expand Down
22 changes: 21 additions & 1 deletion hugr/src/std_extensions/arithmetic/float_ops/const_fold.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
algorithm::const_fold::sorted_consts,
extension::{ConstFold, ConstFoldResult, OpDef},
extension::{prelude::ConstString, ConstFold, ConstFoldResult, OpDef},
ops,
std_extensions::arithmetic::float_types::ConstF64,
IncomingPort,
Expand All @@ -15,6 +15,7 @@ pub(super) fn set_fold(op: &FloatOps, def: &mut OpDef) {
fmax | fmin | fadd | fsub | fmul | fdiv => def.set_constant_folder(BinaryFold::from_op(op)),
feq | fne | flt | fgt | fle | fge => def.set_constant_folder(CmpFold::from_op(*op)),
fneg | fabs | ffloor | fceil => def.set_constant_folder(UnaryFold::from_op(op)),
ftostring => def.set_constant_folder(ToStringFold::from_op(op)),
}
}

Expand Down Expand Up @@ -118,3 +119,22 @@ impl ConstFold for UnaryFold {
Some(vec![(0.into(), res.into())])
}
}

/// Fold string-conversion operations
struct ToStringFold(Box<dyn Fn(f64) -> String + Send + Sync>);
impl ToStringFold {
fn from_op(_op: &FloatOps) -> Self {
Self(Box::new(|x| x.to_string()))
}
}
impl ConstFold for ToStringFold {
fn fold(
&self,
_type_args: &[crate::types::TypeArg],
consts: &[(IncomingPort, ops::Const)],
) -> ConstFoldResult {
let [f] = get_floats(consts)?;
let res = ConstString::new((self.0)(f));
Some(vec![(0.into(), res.into())])
}
}
13 changes: 11 additions & 2 deletions hugr/src/std_extensions/arithmetic/int_ops.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Basic integer operations.
use super::int_types::{get_log_width, int_tv, LOG_WIDTH_TYPE_PARAM};
use crate::extension::prelude::{sum_with_error, BOOL_T};
use crate::extension::prelude::{sum_with_error, BOOL_T, STRING_TYPE};
use crate::extension::simple_op::{MakeExtensionOp, MakeOpDef, MakeRegisteredOp, OpLoadError};
use crate::extension::{
CustomValidator, ExtensionRegistry, OpDef, SignatureFunc, ValidateJustArgs, PRELUDE,
Expand Down Expand Up @@ -91,6 +91,8 @@ pub enum IntOpDef {
ishr,
irotl,
irotr,
itostring_u,
itostring_s,
}

impl MakeOpDef for IntOpDef {
Expand Down Expand Up @@ -154,6 +156,11 @@ impl MakeOpDef for IntOpDef {
ishl | ishr | irotl | irotr => {
int_polytype(2, vec![int_tv(0), int_tv(1)], vec![int_tv(0)]).into()
}
itostring_u | itostring_s => PolyFuncType::new(
vec![LOG_WIDTH_TYPE_PARAM],
FunctionType::new(vec![int_tv(0)], vec![STRING_TYPE]),
)
.into(),
}
}

Expand Down Expand Up @@ -214,6 +221,8 @@ impl MakeOpDef for IntOpDef {
(leftmost bits replace rightmost bits)",
irotr => "rotate first input right by k bits where k is unsigned interpretation of second input \
(rightmost bits replace leftmost bits)",
itostring_s => "convert a signed integer to its string representation",
itostring_u => "convert an unsigned integer to its string representation",
}.into()
}
}
Expand Down Expand Up @@ -339,7 +348,7 @@ mod test {
fn test_int_ops_extension() {
assert_eq!(EXTENSION.name() as &str, "arithmetic.int");
assert_eq!(EXTENSION.types().count(), 0);
assert_eq!(EXTENSION.operations().count(), 45);
assert_eq!(EXTENSION.operations().count(), 47);
for (name, _) in EXTENSION.operations() {
assert!(name.starts_with('i'));
}
Expand Down
21 changes: 17 additions & 4 deletions specification/hugr.md
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,8 @@ so must be supported by all third-party tooling.

`usize`: a positive integer size type.

`string`: a string type.

`array<N, T>`: a known-size (N) array of type T.

`qubit`: a linear (non-copyable) qubit type.
Expand All @@ -1640,10 +1642,15 @@ so must be supported by all third-party tooling.

### Operations

| Name | Inputs | Outputs | Meaning |
|-------------------|-----------|---------------|-----------------------------------------------------------------|
| `new_array<N, T>` | `T` x N | `array<N, T>` | Create an array from all the inputs |
| `panic` | ErrorType | - | Immediately end execution and pass contents of error to context |
| Name | Inputs | Outputs | Meaning |
|-------------------|-----------|---------------|------------------------------------------------------------------ |
| `print` | `string` | - | Append the string to the program's output stream[^1] (atomically) |
| `new_array<N, T>` | `T` x N | `array<N, T>` | Create an array from all the inputs |
| `panic` | ErrorType | - | Immediately end execution and pass contents of error to context |

[^1] The existence of an output stream, and the processing of it either during
or after program execution, is runtime-dependent. If no output stream exists
then the `print` function is a no-op.

### Logic Extension

Expand Down Expand Up @@ -1763,6 +1770,8 @@ Other operations:
| `ishr<N,M>`( \* ) | `int<N>`, `int<M>` | `int<N>` | shift first input right by k bits where k is unsigned interpretation of second input (rightmost bits dropped, leftmost bits set to zero) |
| `irotl<N,M>`( \* ) | `int<N>`, `int<M>` | `int<N>` | rotate first input left by k bits where k is unsigned interpretation of second input (leftmost bits replace rightmost bits) |
| `irotr<N,M>`( \* ) | `int<N>`, `int<M>` | `int<N>` | rotate first input right by k bits where k is unsigned interpretation of second input (rightmost bits replace leftmost bits) |
| `itostring_u<N>` | `int<N>` | `string` | decimal string representation of unsigned integer |
| `itostring_s<N>` | `int<N>` | `string` | decimal string representation of signed integer |

#### `arithmetic.float.types`

Expand Down Expand Up @@ -1794,6 +1803,10 @@ except where stated.
| `fdiv` | `float64`, `float64` | `float64` | division |
| `ffloor` | `float64` | `float64` | floor |
| `fceil` | `float64` | `float64` | ceiling |
| `ftostring` | `float64` | `string` | string representation[^1] |

[^1] The exact specification of the float-to-string conversion is
implementation-dependent.

#### `arithmetic.conversions`

Expand Down

0 comments on commit b7df2ef

Please sign in to comment.