From 067d051936a63868963f59aa16bfd0de37bb3ed7 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Wed, 17 Jan 2024 19:03:10 +0300 Subject: [PATCH] feat: add support for hexes in constants --- CHANGELOG.md | 1 + assembly/src/ast/parsers/io_ops.rs | 74 +------------------ assembly/src/ast/parsers/mod.rs | 74 ++++++++++++++++++- assembly/src/tests.rs | 18 +++++ .../user_docs/assembly/code_organization.md | 2 +- 5 files changed, 96 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e83f70278..f41f93d501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Introduced the `procref.` 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` diff --git a/assembly/src/ast/parsers/io_ops.rs b/assembly/src/ast/parsers/io_ops.rs index 9b2eb1f179..579ee36183 100644 --- a/assembly/src/ast/parsers/io_ops.rs +++ b/assembly/src/ast/parsers/io_ops.rs @@ -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 // ================================================================================================ @@ -315,67 +308,6 @@ fn parse_long_hex_param(op: &Token, hex_str: &str) -> Result 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 { - 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 { diff --git a/assembly/src/ast/parsers/mod.rs b/assembly/src/ast/parsers/mod.rs index d199514ea5..40456df6f2 100644 --- a/assembly/src/ast/parsers/mod.rs +++ b/assembly/src/ast/parsers/mod.rs @@ -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; @@ -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 // ================================================================================================ @@ -109,7 +117,10 @@ fn parse_const_value( ) -> Result { let result = match const_value.parse::() { 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 { @@ -217,3 +228,64 @@ fn parse_error_code(token: &Token, constants: &LocalConstMap) -> Result 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 { + 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) + } +} diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index 7f873626bd..f76fbc4e32 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -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(); diff --git a/docs/src/user_docs/assembly/code_organization.md b/docs/src/user_docs/assembly/code_organization.md index 3208d63d16..a22af8b044 100644 --- a/docs/src/user_docs/assembly/code_organization.md +++ b/docs/src/user_docs/assembly/code_organization.md @@ -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