diff --git a/ast/src/reducer/canonicalization.rs b/ast/src/reducer/canonicalization.rs index 7237d88d69..674c55ac5f 100644 --- a/ast/src/reducer/canonicalization.rs +++ b/ast/src/reducer/canonicalization.rs @@ -485,6 +485,20 @@ impl ReconstructingReducer for Canonicalizer { } } + fn reduce_string(&mut self, string: &[char], span: &Span) -> Result { + let mut elements = Vec::new(); + for character in string { + elements.push(SpreadOrExpression::Expression(Expression::Value( + ValueExpression::Char(*character, span.clone()), + ))); + } + + Ok(Expression::ArrayInline(ArrayInlineExpression { + elements, + span: span.clone(), + })) + } + fn reduce_array_init( &mut self, array_init: &ArrayInitExpression, diff --git a/ast/src/reducer/reconstructing_director.rs b/ast/src/reducer/reconstructing_director.rs index 942eaec3aa..bb727b4186 100644 --- a/ast/src/reducer/reconstructing_director.rs +++ b/ast/src/reducer/reconstructing_director.rs @@ -51,7 +51,7 @@ impl ReconstructingDirector { pub fn reduce_expression(&mut self, expression: &Expression) -> Result { let new = match expression { Expression::Identifier(identifier) => Expression::Identifier(self.reduce_identifier(&identifier)?), - Expression::Value(value) => Expression::Value(self.reduce_value(&value)?), + Expression::Value(value) => self.reduce_value(&value)?, Expression::Binary(binary) => Expression::Binary(self.reduce_binary(&binary)?), Expression::Unary(unary) => Expression::Unary(self.reduce_unary(&unary)?), Expression::Ternary(ternary) => Expression::Ternary(self.reduce_ternary(&ternary)?), @@ -100,12 +100,17 @@ impl ReconstructingDirector { self.reducer.reduce_group_value(group_value, new) } - pub fn reduce_value(&mut self, value: &ValueExpression) -> Result { + pub fn reduce_string(&mut self, string: &[char], span: &Span) -> Result { + self.reducer.reduce_string(string, span) + } + + pub fn reduce_value(&mut self, value: &ValueExpression) -> Result { let new = match value { ValueExpression::Group(group_value) => { - ValueExpression::Group(Box::new(self.reduce_group_value(&group_value)?)) + Expression::Value(ValueExpression::Group(Box::new(self.reduce_group_value(&group_value)?))) } - _ => value.clone(), + ValueExpression::String(string, span) => self.reduce_string(&string, &span)?, + _ => Expression::Value(value.clone()), }; self.reducer.reduce_value(value, new) diff --git a/ast/src/reducer/reconstructing_reducer.rs b/ast/src/reducer/reconstructing_reducer.rs index 36c8972728..0af238d2b8 100644 --- a/ast/src/reducer/reconstructing_reducer.rs +++ b/ast/src/reducer/reconstructing_reducer.rs @@ -51,11 +51,14 @@ pub trait ReconstructingReducer { Ok(new) } - fn reduce_value( - &mut self, - _value: &ValueExpression, - new: ValueExpression, - ) -> Result { + fn reduce_string(&mut self, string: &[char], span: &Span) -> Result { + Ok(Expression::Value(ValueExpression::String( + string.to_vec(), + span.clone(), + ))) + } + + fn reduce_value(&mut self, _value: &ValueExpression, new: Expression) -> Result { Ok(new) } diff --git a/compiler/src/phases/reducing_director.rs b/compiler/src/phases/reducing_director.rs index 0bec948eb3..0138ca6d67 100644 --- a/compiler/src/phases/reducing_director.rs +++ b/compiler/src/phases/reducing_director.rs @@ -152,9 +152,7 @@ impl CombineAstAsgDirector { asg: &AsgExpression, ) -> Result { let new = match (ast, asg) { - (AstExpression::Value(value), AsgExpression::Constant(const_)) => { - AstExpression::Value(self.reduce_value(&value, &const_)?) - } + (AstExpression::Value(value), AsgExpression::Constant(const_)) => self.reduce_value(&value, &const_)?, (AstExpression::Binary(ast), AsgExpression::Binary(asg)) => { AstExpression::Binary(self.reduce_binary(&ast, &asg)?) } @@ -404,7 +402,7 @@ impl CombineAstAsgDirector { self.ast_reducer.reduce_unary(ast, inner, ast.op.clone()) } - pub fn reduce_value(&mut self, ast: &ValueExpression, asg: &AsgConstant) -> Result { + pub fn reduce_value(&mut self, ast: &ValueExpression, asg: &AsgConstant) -> Result { let mut new = ast.clone(); if self.options.type_inference_enabled() { @@ -444,7 +442,7 @@ impl CombineAstAsgDirector { } } - self.ast_reducer.reduce_value(ast, new) + self.ast_reducer.reduce_value(ast, AstExpression::Value(new)) } pub fn reduce_variable_ref( diff --git a/compiler/src/value/value.rs b/compiler/src/value/value.rs index 1bb94ed34b..645fb5555f 100644 --- a/compiler/src/value/value.rs +++ b/compiler/src/value/value.rs @@ -99,14 +99,22 @@ impl<'a, F: PrimeField, G: GroupType> fmt::Display for ConstrainedValue<'a, F // Data type wrappers ConstrainedValue::Array(ref array) => { - write!(f, "[")?; - for (i, e) in array.iter().enumerate() { - write!(f, "{}", e)?; - if i < array.len() - 1 { - write!(f, ", ")?; + if matches!(array[0], ConstrainedValue::Char(_)) { + for character in array { + write!(f, "{}", character)?; } + + Ok(()) + } else { + write!(f, "[")?; + for (i, e) in array.iter().enumerate() { + write!(f, "{}", e)?; + if i < array.len() - 1 { + write!(f, ", ")?; + } + } + write!(f, "]") } - write!(f, "]") } ConstrainedValue::Tuple(ref tuple) => { let values = tuple.iter().map(|x| x.to_string()).collect::>().join(", "); diff --git a/compiler/tests/canonicalization/mod.rs b/compiler/tests/canonicalization/mod.rs index f420b2370c..5a86dcf4b2 100644 --- a/compiler/tests/canonicalization/mod.rs +++ b/compiler/tests/canonicalization/mod.rs @@ -95,3 +95,16 @@ fn test_illegal_array_range_fail() { let program = parse_program(program_string); assert!(program.is_err()); } + +#[test] +fn test_string_transformation() { + let program_string = include_str!("string_transformation.leo"); + let program = parse_program(program_string).unwrap(); + assert_satisfied(program); + + let ast = parse_program_ast(program_string); + let expected_json = include_str!("string_transformation.json"); + let expected_ast: Ast = Ast::from_json_string(expected_json).expect("Unable to parse json."); + + assert_eq!(expected_ast, ast); +} diff --git a/compiler/tests/canonicalization/string_transformation.json b/compiler/tests/canonicalization/string_transformation.json new file mode 100644 index 0000000000..03744195c0 --- /dev/null +++ b/compiler/tests/canonicalization/string_transformation.json @@ -0,0 +1,310 @@ +{ + "name": "", + "expected_input": [], + "imports": [], + "circuits": {}, + "global_consts": {}, + "functions": { + "{\"name\":\"main\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":10,\\\"col_stop\\\":14,\\\"path\\\":\\\"\\\",\\\"content\\\":\\\"function main() {\\\"}\"}": { + "annotations": [], + "identifier": "{\"name\":\"main\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":10,\\\"col_stop\\\":14,\\\"path\\\":\\\"\\\",\\\"content\\\":\\\"function main() {\\\"}\"}", + "input": [], + "output": { + "Tuple": [] + }, + "block": { + "statements": [ + { + "Definition": { + "declaration_type": "Let", + "variable_names": [ + { + "mutable": true, + "identifier": "{\"name\":\"s\",\"span\":\"{\\\"line_start\\\":2,\\\"line_stop\\\":2,\\\"col_start\\\":9,\\\"col_stop\\\":10,\\\"path\\\":\\\"\\\",\\\"content\\\":\\\" let s = `Hello, World!`;\\\"}\"}", + "span": { + "line_start": 2, + "line_stop": 2, + "col_start": 9, + "col_stop": 10, + "path": "", + "content": " let s = `Hello, World!`;" + } + } + ], + "type_": { + "Array": [ + "Char", + [ + { + "value": "13" + } + ] + ] + }, + "value": { + "ArrayInline": { + "elements": [ + { + "Expression": { + "Value": { + "Char": [ + "H", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "e", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "l", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "l", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "o", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + ",", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + " ", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "W", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "o", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "r", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "l", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "d", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "!", + { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + ] + } + } + } + ], + "span": { + "line_start": 2, + "line_stop": 2, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + } + }, + "span": { + "line_start": 2, + "line_stop": 2, + "col_start": 5, + "col_stop": 28, + "path": "", + "content": " let s = `Hello, World!`;" + } + } + } + ], + "span": { + "line_start": 1, + "line_stop": 3, + "col_start": 17, + "col_stop": 2, + "path": "", + "content": "function main() {\n...\n}" + } + }, + "span": { + "line_start": 1, + "line_stop": 3, + "col_start": 1, + "col_stop": 2, + "path": "", + "content": "function main() {\n...\n}" + } + } + } + } + \ No newline at end of file diff --git a/compiler/tests/canonicalization/string_transformation.leo b/compiler/tests/canonicalization/string_transformation.leo new file mode 100644 index 0000000000..0f2e78e1a9 --- /dev/null +++ b/compiler/tests/canonicalization/string_transformation.leo @@ -0,0 +1,3 @@ +function main() { + let s = `Hello, World!`; +} \ No newline at end of file diff --git a/compiler/tests/type_inference/basic.json b/compiler/tests/type_inference/basic.json index 2c6b796cb9..fdd3bf2e15 100644 --- a/compiler/tests/type_inference/basic.json +++ b/compiler/tests/type_inference/basic.json @@ -1027,11 +1027,283 @@ "content": " const n = 'a';" } } + }, + { + "Definition": { + "declaration_type": "Const", + "variable_names": [ + { + "mutable": false, + "identifier": "{\"name\":\"o\",\"span\":\"{\\\"line_start\\\":24,\\\"line_stop\\\":24,\\\"col_start\\\":9,\\\"col_stop\\\":10,\\\"path\\\":\\\"\\\",\\\"content\\\":\\\" const o = `Hello, World!`;\\\"}\"}", + "span": { + "line_start": 24, + "line_stop": 24, + "col_start": 9, + "col_stop": 10, + "path": "", + "content": " const o = `Hello, World!`;" + } + } + ], + "type_": { + "Array": [ + "Char", + [ + { + "value": "13" + } + ] + ] + }, + "value": { + "ArrayInline": { + "elements": [ + { + "Expression": { + "Value": { + "Char": [ + "H", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "e", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "l", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "l", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "o", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + ",", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + " ", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "W", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "o", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "r", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "l", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "d", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + }, + { + "Expression": { + "Value": { + "Char": [ + "!", + { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + ] + } + } + } + ], + "span": { + "line_start": 24, + "line_stop": 24, + "col_start": 13, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + } + }, + "span": { + "line_start": 24, + "line_stop": 24, + "col_start": 3, + "col_stop": 28, + "path": "", + "content": " const o = `Hello, World!`;" + } + } } ], "span": { "line_start": 9, - "line_stop": 24, + "line_stop": 25, "col_start": 17, "col_stop": 2, "path": "", @@ -1040,11 +1312,11 @@ }, "span": { "line_start": 9, - "line_stop": 24, + "line_stop": 25, "col_start": 1, "col_stop": 2, "path": "", - "content": "function main() {\n...\n}\n\n\n\n\n\n\n\n\n\n\n\n\n" + "content": "function main() {\n...\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n" } } } diff --git a/compiler/tests/type_inference/basic.leo b/compiler/tests/type_inference/basic.leo index 330daeae20..f3dae7a734 100644 --- a/compiler/tests/type_inference/basic.leo +++ b/compiler/tests/type_inference/basic.leo @@ -21,4 +21,5 @@ function main() { const l = (1u8, 1u8, true); const m = Foo {}; const n = 'a'; + const o = `Hello, World!`; } \ No newline at end of file diff --git a/input/src/leo-input.pest b/input/src/leo-input.pest index a500de21fb..b072fb63d8 100644 --- a/input/src/leo-input.pest +++ b/input/src/leo-input.pest @@ -148,7 +148,7 @@ char_types = { } // Declared in values/char_value.rs -value_char = { "\'" ~ char_types ~ "\'" } +value_char = ${ "\'" ~ char_types ~ "\'" } // Declared in values/integer_value.rs value_integer = { value_integer_signed | value_integer_unsigned} diff --git a/parser/src/tokenizer/lexer.rs b/parser/src/tokenizer/lexer.rs index d83438c58d..b8569c6673 100644 --- a/parser/src/tokenizer/lexer.rs +++ b/parser/src/tokenizer/lexer.rs @@ -154,7 +154,7 @@ impl Token { } continue; } - + return (0, None); } diff --git a/tests/compiler/global_consts/global_const_types.leo b/tests/compiler/global_consts/global_const_types.leo index 5b454dbfc7..5fba7a9316 100644 --- a/tests/compiler/global_consts/global_const_types.leo +++ b/tests/compiler/global_consts/global_const_types.leo @@ -20,6 +20,8 @@ const field_test: field = 2; const use_another_const = basic + 1; const foo = Foo { width: 10, height: 20 }; const uno = uno(); +const character = 'a'; +const hello = `Hello, World!`; circuit Foo { width: u32, @@ -47,5 +49,7 @@ function main(a: u32) -> bool { && use_another_const == 9u32 // use another const test && foo.width == 10u32 // circuit test && foo.height == 20u32 - && uno == 1u32; // function test + && uno == 1u32 // function test + && character == 'a' // char test + && hello == `Hello, World!`; } diff --git a/tests/compiler/string/circuit.leo b/tests/compiler/string/circuit.leo new file mode 100644 index 0000000000..43326849f2 --- /dev/null +++ b/tests/compiler/string/circuit.leo @@ -0,0 +1,22 @@ +/* +namespace: Compile +expectation: Pass +input_file: + - inputs/string_out.in +*/ + +circuit Foo { + s1: [char; 13]; +} + +function takes_string(s: [char; 13]) -> bool { + return s == `Hello, World!`; +} + +function main(s1: [char; 13]) -> [char; 13] { + let f = Foo { s1 }; + let b = takes_string(s1); + + let result = f.s1 == `Hello, World!` ? s1 : `abcdefghjklmn`; + return result; +} \ No newline at end of file diff --git a/tests/compiler/string/equality.leo b/tests/compiler/string/equality.leo new file mode 100644 index 0000000000..460941b18b --- /dev/null +++ b/tests/compiler/string/equality.leo @@ -0,0 +1,11 @@ +/* +namespace: Compile +expectation: Pass +input_file: + - inputs/string.in +*/ + +function main(s1: [char; 13], s2: [char; 4]) -> bool { + let hello: [char; 13] = `Hello, World!`; + return hello == s1 && `nope` != s2; +} \ No newline at end of file diff --git a/tests/compiler/string/inputs/string.in b/tests/compiler/string/inputs/string.in new file mode 100644 index 0000000000..8787f7f0a3 --- /dev/null +++ b/tests/compiler/string/inputs/string.in @@ -0,0 +1,6 @@ +[main] +s1: [char; 13] = ['H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!']; +s2: [char; 4] = ['t', 'e', 's', 't']; + +[registers] +out: bool = true; \ No newline at end of file diff --git a/tests/compiler/string/inputs/string_out.in b/tests/compiler/string/inputs/string_out.in new file mode 100644 index 0000000000..be00dfe2c0 --- /dev/null +++ b/tests/compiler/string/inputs/string_out.in @@ -0,0 +1,6 @@ +[main] +s1: [char; 13] = ['H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!']; +s2: [char; 4] = ['t', 'e', 's', 't']; + +[registers] +out: [char; 13] = ['H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!']; \ No newline at end of file diff --git a/tests/expectations/compiler/compiler/string/circuit.leo.out b/tests/expectations/compiler/compiler/string/circuit.leo.out new file mode 100644 index 0000000000..b3a7b67aa3 --- /dev/null +++ b/tests/expectations/compiler/compiler/string/circuit.leo.out @@ -0,0 +1,18 @@ +--- +namespace: Compile +expectation: Pass +outputs: + - circuit: + num_public_variables: 0 + num_private_variables: 141 + num_constraints: 115 + at: 145ada587c833434abb89c3349d19e06365fda3eb9b2a227046a78469e3ca313 + bt: f2945a3bc1beaee407bb4ec35303115a93a8c68886d97011cd65ec6d899664e8 + ct: 10b997b6341b3cf811cb7b0fdb891f91006d41c50e9f9566ff92f92816153dfc + output: + - input_file: inputs/string_out.in + output: + registers: + out: + type: "[char; 13]" + value: "Hello, World!" diff --git a/tests/expectations/compiler/compiler/string/equality.leo.out b/tests/expectations/compiler/compiler/string/equality.leo.out new file mode 100644 index 0000000000..21cc0616d7 --- /dev/null +++ b/tests/expectations/compiler/compiler/string/equality.leo.out @@ -0,0 +1,18 @@ +--- +namespace: Compile +expectation: Pass +outputs: + - circuit: + num_public_variables: 0 + num_private_variables: 84 + num_constraints: 67 + at: da464aeb42d53f56ff26141c802d2a769477763766c5746e603c5326b01790bb + bt: 6b03d4cb03e7bf9cf8ec746ee3410578d8ac51a29e56f9090d8e27a4ddf16c64 + ct: ebcd3f740af33d9c3ab2c5e4189709be8d73fab149e788734705cad488a4208c + output: + - input_file: inputs/string.in + output: + registers: + out: + type: bool + value: "true"