Skip to content

Commit

Permalink
Merge pull request #1199 from 0xPolygonMiden/andrew-support-hex-in-co…
Browse files Browse the repository at this point in the history
…nstants

Support hexadecimal literals in constants
  • Loading branch information
Fumuran authored Jan 17, 2024
2 parents a9a1c6a + 067d051 commit ee45c70
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Introduced the `procref.<proc_name>` assembly instruction (#1113).
- Added the ability to use constants as counters in `repeat` loops (#1124).
- All `checked` versions of the u32 instructions were removed. All `unchecked` versions were renamed: this mode specification was removed from their titles (#1115).
- Added support for hexadecimal values in constants (#1199).

#### Stdlib
- Introduced `std::utils` module with `is_empty_word` procedure. Refactored `std::collections::smt`
Expand Down
74 changes: 3 additions & 71 deletions assembly/src/ast/parsers/io_ops.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
use super::{
parse_checked_param, parse_param_with_constant_lookup, Felt,
parse_checked_param, parse_hex_value, parse_param_with_constant_lookup, Endianness, Felt,
Instruction::*,
LocalConstMap,
Node::{self, Instruction},
ParsingError, Token, Vec, CONSTANT_LABEL_PARSER,
ParsingError, Token, Vec, CONSTANT_LABEL_PARSER, HEX_CHUNK_SIZE,
};
use crate::{StarkField, ADVICE_READ_LIMIT, HEX_CHUNK_SIZE, MAX_PUSH_INPUTS};
use crate::{StarkField, ADVICE_READ_LIMIT, MAX_PUSH_INPUTS};
use core::{convert::TryFrom, ops::RangeBounds};
use vm_core::WORD_SIZE;

/// Helper enum for endianness determination in the parsing functions.
#[derive(Debug)]
enum Endianness {
Little,
Big,
}

// CONSTANTS
// ================================================================================================

Expand Down Expand Up @@ -315,67 +308,6 @@ fn parse_long_hex_param(op: &Token, hex_str: &str) -> Result<Node, ParsingError>
build_push_many_instruction(values)
}

/// Parses a hexadecimal parameter value into a u64.
///
/// # Errors
/// Returns an error if:
/// - The length of a short hex string (big-endian) is not even.
/// - The length of a short hex string (big-endian) is greater than 16.
/// - The length of the chunk of a long hex string (little-endian) is not equal to 16.
/// - If the string does not contain a valid hexadecimal value.
/// - If the parsed value is greater than or equal to the field modulus.
fn parse_hex_value(
op: &Token,
hex_str: &str,
param_idx: usize,
endianness: Endianness,
) -> Result<u64, ParsingError> {
let value = match endianness {
Endianness::Big => {
if hex_str.len() % 2 != 0 {
return Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!(
"hex string '{hex_str}' does not contain an even number of characters"
),
));
}
if hex_str.len() > HEX_CHUNK_SIZE {
return Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!("hex string '{hex_str}' contains too many characters"),
));
}
u64::from_str_radix(hex_str, 16)
.map_err(|_| ParsingError::invalid_param(op, param_idx))?
}
Endianness::Little => {
if hex_str.len() != HEX_CHUNK_SIZE {
return Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!("hex string chunk '{hex_str}' must contain exactly 16 characters"),
));
}
u64::from_str_radix(hex_str, 16)
.map(|v| v.swap_bytes())
.map_err(|_| ParsingError::invalid_param(op, param_idx))?
}
};

if value >= Felt::MODULUS {
Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!("hex string '{hex_str}' contains value greater than field modulus"),
))
} else {
Ok(value)
}
}

/// Determines the minimal type appropriate for provided value and returns appropriate instruction
/// for this value
fn build_push_one_instruction(value: u64) -> Result<Node, ParsingError> {
Expand Down
74 changes: 73 additions & 1 deletion assembly/src/ast/parsers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use super::{
SliceReader, StarkField, String, ToString, Token, TokenStream, Vec, MAX_BODY_LEN, MAX_DOCS_LEN,
MAX_LABEL_LEN, MAX_STACK_WORD_OFFSET,
};
use crate::HEX_CHUNK_SIZE;
use core::{fmt::Display, ops::RangeBounds};

mod adv_ops;
Expand All @@ -28,6 +29,13 @@ pub use labels::{
PROCEDURE_LABEL_PARSER,
};

/// Helper enum for endianness determination in the parsing functions.
#[derive(Debug)]
pub enum Endianness {
Little,
Big,
}

// PARSERS FUNCTIONS
// ================================================================================================

Expand Down Expand Up @@ -109,7 +117,10 @@ fn parse_const_value(
) -> Result<u64, ParsingError> {
let result = match const_value.parse::<u64>() {
Ok(value) => value,
Err(_) => calculate_const_value(op, const_value, constants)?.as_int(),
Err(_) => match const_value.strip_prefix("0x") {
Some(param_str) => parse_hex_value(op, param_str, 1, Endianness::Big)?,
None => calculate_const_value(op, const_value, constants)?.as_int(),
},
};

if result >= Felt::MODULUS {
Expand Down Expand Up @@ -217,3 +228,64 @@ fn parse_error_code(token: &Token, constants: &LocalConstMap) -> Result<u32, Par
_ => Err(ParsingError::extra_param(token)),
}
}

/// Parses a hexadecimal parameter value into a u64.
///
/// # Errors
/// Returns an error if:
/// - The length of a short hex string (big-endian) is not even.
/// - The length of a short hex string (big-endian) is greater than 16.
/// - The length of the chunk of a long hex string (little-endian) is not equal to 16.
/// - If the string does not contain a valid hexadecimal value.
/// - If the parsed value is greater than or equal to the field modulus.
fn parse_hex_value(
op: &Token,
hex_str: &str,
param_idx: usize,
endianness: Endianness,
) -> Result<u64, ParsingError> {
let value = match endianness {
Endianness::Big => {
if hex_str.len() % 2 != 0 {
return Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!(
"hex string '{hex_str}' does not contain an even number of characters"
),
));
}
if hex_str.len() > HEX_CHUNK_SIZE {
return Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!("hex string '{hex_str}' contains too many characters"),
));
}
u64::from_str_radix(hex_str, 16)
.map_err(|_| ParsingError::invalid_param(op, param_idx))?
}
Endianness::Little => {
if hex_str.len() != HEX_CHUNK_SIZE {
return Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!("hex string chunk '{hex_str}' must contain exactly 16 characters"),
));
}
u64::from_str_radix(hex_str, 16)
.map(|v| v.swap_bytes())
.map_err(|_| ParsingError::invalid_param(op, param_idx))?
}
};

if value >= Felt::MODULUS {
Err(ParsingError::invalid_param_with_reason(
op,
param_idx,
&format!("hex string '{hex_str}' contains value greater than field modulus"),
))
} else {
Ok(value)
}
}
18 changes: 18 additions & 0 deletions assembly/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,24 @@ fn constant_alphanumeric_expression() {
assert_eq!(expected, format!("{program}"));
}

#[test]
fn constant_hexadecimal_value() {
let assembler = Assembler::default();
let source = "const.TEST_CONSTANT=0xFF \
begin \
push.TEST_CONSTANT \
end \
";
let expected = "\
begin \
span \
push(255) \
end \
end";
let program = assembler.compile(source).unwrap();
assert_eq!(expected, format!("{program}"));
}

#[test]
fn constant_field_division() {
let assembler = Assembler::default();
Expand Down
2 changes: 1 addition & 1 deletion docs/src/user_docs/assembly/code_organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Miden assembly supports constant declarations. These constants are scoped to the

Constants must be declared right after module imports and before any procedures or program bodies. A constant's name must start with an upper-case letter and can contain any combination of numbers, upper-case ASCII letters, and underscores (`_`). The number of characters in a constant name cannot exceed 100.

A constant's value must be in the range between $0$ and $2^{64} - 2^{32}$ (both inclusive) and can be defined by an arithmetic expression using `+`, `-`, `*`, `/`, `//`, `(`, `)` operators and references to the previously defined constants. Here `/` is a field division and `//` is an integer division. Note that the arithmetic expression cannot contain spaces.
A constant's value must be in a decimal or hexidecimal form and be in the range between $0$ and $2^{64} - 2^{32}$ (both inclusive). Value can be defined by an arithmetic expression using `+`, `-`, `*`, `/`, `//`, `(`, `)` operators and references to the previously defined constants if it uses only decimal numbers. Here `/` is a field division and `//` is an integer division. Note that the arithmetic expression cannot contain spaces.

```
use.std::math::u64
Expand Down

0 comments on commit ee45c70

Please sign in to comment.