diff --git a/asg/src/checks/return_path.rs b/asg/src/checks/return_path.rs index e0f73d3c0d..ed8874deb2 100644 --- a/asg/src/checks/return_path.rs +++ b/asg/src/checks/return_path.rs @@ -92,7 +92,7 @@ impl<'a> MonoidalReducerStatement<'a, BoolAnd> for ReturnPathReducer { if_true.append(if_false.unwrap_or(BoolAnd(false))) } - fn reduce_formatted_string(&mut self, input: &FormatString, parameters: Vec) -> BoolAnd { + fn reduce_formatted_string(&mut self, input: &ConsoleArgs, parameters: Vec) -> BoolAnd { BoolAnd(false) } diff --git a/asg/src/const_value.rs b/asg/src/const_value.rs index 5a1c57d2a7..d430f0bbc4 100644 --- a/asg/src/const_value.rs +++ b/asg/src/const_value.rs @@ -109,13 +109,29 @@ pub enum CharValue { NonScalar(u32), } +impl From<&leo_ast::Char> for CharValue { + fn from(other: &leo_ast::Char) -> Self { + use leo_ast::Char::*; + match other { + Scalar(value) => CharValue::Scalar(*value), + NonScalar(value) => CharValue::NonScalar(*value), + } + } +} + +impl Into for &CharValue { + fn into(self) -> leo_ast::Char { + use leo_ast::Char::*; + match self { + CharValue::Scalar(value) => Scalar(*value), + CharValue::NonScalar(value) => NonScalar(*value), + } + } +} + impl From for CharValue { fn from(other: leo_ast::CharValue) -> Self { - use leo_ast::Char::*; - match other.character { - Scalar(value) => CharValue::Scalar(value), - NonScalar(value) => CharValue::NonScalar(value), - } + Self::from(&other.character) } } diff --git a/asg/src/reducer/monoidal_director.rs b/asg/src/reducer/monoidal_director.rs index b0f97fec7b..ce34755a11 100644 --- a/asg/src/reducer/monoidal_director.rs +++ b/asg/src/reducer/monoidal_director.rs @@ -225,7 +225,7 @@ impl<'a, T: Monoid, R: MonoidalReducerStatement<'a, T>> MonoidalDirector<'a, T, .reduce_conditional_statement(input, condition, if_true, if_false) } - pub fn reduce_formatted_string(&mut self, input: &FormatString<'a>) -> T { + pub fn reduce_formatted_string(&mut self, input: &ConsoleArgs<'a>) -> T { let parameters = input .parameters .iter() diff --git a/asg/src/reducer/monoidal_reducer.rs b/asg/src/reducer/monoidal_reducer.rs index 2a10604e1e..9510417241 100644 --- a/asg/src/reducer/monoidal_reducer.rs +++ b/asg/src/reducer/monoidal_reducer.rs @@ -118,7 +118,7 @@ pub trait MonoidalReducerStatement<'a, T: Monoid>: MonoidalReducerExpression<'a, condition.append(if_true).append_option(if_false) } - fn reduce_formatted_string(&mut self, input: &FormatString<'a>, parameters: Vec) -> T { + fn reduce_formatted_string(&mut self, input: &ConsoleArgs<'a>, parameters: Vec) -> T { T::default().append_all(parameters.into_iter()) } diff --git a/asg/src/reducer/reconstructing_director.rs b/asg/src/reducer/reconstructing_director.rs index 080eb1c5db..75127d477a 100644 --- a/asg/src/reducer/reconstructing_director.rs +++ b/asg/src/reducer/reconstructing_director.rs @@ -243,7 +243,7 @@ impl<'a, R: ReconstructingReducerStatement<'a>> ReconstructingDirector<'a, R> { .reduce_conditional_statement(input, condition, if_true, if_false) } - pub fn reduce_formatted_string(&mut self, input: FormatString<'a>) -> FormatString<'a> { + pub fn reduce_formatted_string(&mut self, input: ConsoleArgs<'a>) -> ConsoleArgs<'a> { let parameters = input .parameters .iter() diff --git a/asg/src/reducer/reconstructing_reducer.rs b/asg/src/reducer/reconstructing_reducer.rs index f42a707bc8..cf4e617ec4 100644 --- a/asg/src/reducer/reconstructing_reducer.rs +++ b/asg/src/reducer/reconstructing_reducer.rs @@ -274,12 +274,12 @@ pub trait ReconstructingReducerStatement<'a>: ReconstructingReducerExpression<'a fn reduce_formatted_string( &mut self, - input: FormatString<'a>, + input: ConsoleArgs<'a>, parameters: Vec<&'a Expression<'a>>, - ) -> FormatString<'a> { - FormatString { + ) -> ConsoleArgs<'a> { + ConsoleArgs { span: input.span, - parts: input.parts, + string: input.string, parameters: parameters.into_iter().map(Cell::new).collect(), } } @@ -293,7 +293,7 @@ pub trait ReconstructingReducerStatement<'a>: ReconstructingReducerExpression<'a }) } - fn reduce_console_log(&mut self, input: ConsoleStatement<'a>, argument: FormatString<'a>) -> Statement<'a> { + fn reduce_console_log(&mut self, input: ConsoleStatement<'a>, argument: ConsoleArgs<'a>) -> Statement<'a> { assert!(!matches!(input.function, ConsoleFunction::Assert(_))); Statement::Console(ConsoleStatement { parent: input.parent, diff --git a/asg/src/reducer/visitor.rs b/asg/src/reducer/visitor.rs index 0bd16527bd..1e0b46fd9e 100644 --- a/asg/src/reducer/visitor.rs +++ b/asg/src/reducer/visitor.rs @@ -120,7 +120,7 @@ pub trait StatementVisitor<'a>: ExpressionVisitor<'a> { Default::default() } - fn visit_formatted_string(&mut self, input: &FormatString<'a>) -> VisitResult { + fn visit_formatted_string(&mut self, input: &ConsoleArgs<'a>) -> VisitResult { Default::default() } diff --git a/asg/src/reducer/visitor_director.rs b/asg/src/reducer/visitor_director.rs index ee117dffb8..dc6dae6929 100644 --- a/asg/src/reducer/visitor_director.rs +++ b/asg/src/reducer/visitor_director.rs @@ -319,7 +319,7 @@ impl<'a, R: StatementVisitor<'a>> VisitorDirector<'a, R> { } } - pub fn visit_formatted_string(&mut self, input: &FormatString<'a>) -> ConcreteVisitResult { + pub fn visit_formatted_string(&mut self, input: &ConsoleArgs<'a>) -> ConcreteVisitResult { match self.visitor.visit_formatted_string(input) { VisitResult::VisitChildren => { for parameter in input.parameters.iter() { diff --git a/asg/src/statement/console.rs b/asg/src/statement/console.rs index 5e47cb0012..59992f85a2 100644 --- a/asg/src/statement/console.rs +++ b/asg/src/statement/console.rs @@ -14,15 +14,15 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{AsgConvertError, Expression, FromAst, Node, PartialType, Scope, Span, Statement, Type}; -use leo_ast::{ConsoleFunction as AstConsoleFunction, FormatStringPart}; +use crate::{AsgConvertError, CharValue, Expression, FromAst, Node, PartialType, Scope, Span, Statement, Type}; +use leo_ast::ConsoleFunction as AstConsoleFunction; use std::cell::Cell; // TODO (protryon): Refactor to not require/depend on span #[derive(Clone)] -pub struct FormatString<'a> { - pub parts: Vec, +pub struct ConsoleArgs<'a> { + pub string: Vec, pub parameters: Vec>>, pub span: Span, } @@ -30,9 +30,9 @@ pub struct FormatString<'a> { #[derive(Clone)] pub enum ConsoleFunction<'a> { Assert(Cell<&'a Expression<'a>>), - Debug(FormatString<'a>), - Error(FormatString<'a>), - Log(FormatString<'a>), + Debug(ConsoleArgs<'a>), + Error(ConsoleArgs<'a>), + Log(ConsoleArgs<'a>), } #[derive(Clone)] @@ -48,41 +48,42 @@ impl<'a> Node for ConsoleStatement<'a> { } } -impl<'a> FromAst<'a, leo_ast::FormatString> for FormatString<'a> { +impl<'a> FromAst<'a, leo_ast::ConsoleArgs> for ConsoleArgs<'a> { fn from_ast( scope: &'a Scope<'a>, - value: &leo_ast::FormatString, + value: &leo_ast::ConsoleArgs, _expected_type: Option>, ) -> Result { - let expected_param_len = value - .parts - .iter() - .filter(|x| matches!(x, FormatStringPart::Container)) - .count(); - if value.parameters.len() != expected_param_len { - // + 1 for formatting string as to not confuse user - return Err(AsgConvertError::unexpected_call_argument_count( - expected_param_len + 1, - value.parameters.len() + 1, - &value.span, - )); - } + // let expected_param_len = value + // .parts + // .iter() + // .filter(|x| matches!(x, FormatStringPart::Container)) + // .count(); + // if value.parameters.len() != expected_param_len { + // + 1 for formatting string as to not confuse user + // return Err(AsgConvertError::unexpected_call_argument_count( + // expected_param_len + 1, + // value.parameters.len() + 1, + // &value.span, + // )); + // } + let mut parameters = vec![]; for parameter in value.parameters.iter() { parameters.push(Cell::new(<&Expression<'a>>::from_ast(scope, parameter, None)?)); } - Ok(FormatString { - parts: value.parts.clone(), + Ok(ConsoleArgs { + string: value.string.iter().map(CharValue::from).collect::>(), parameters, span: value.span.clone(), }) } } -impl<'a> Into for &FormatString<'a> { - fn into(self) -> leo_ast::FormatString { - leo_ast::FormatString { - parts: self.parts.clone(), +impl<'a> Into for &ConsoleArgs<'a> { + fn into(self) -> leo_ast::ConsoleArgs { + leo_ast::ConsoleArgs { + string: self.string.iter().map(|c| c.into()).collect::>(), parameters: self.parameters.iter().map(|e| e.get().into()).collect(), span: self.span.clone(), } @@ -102,15 +103,9 @@ impl<'a> FromAst<'a, leo_ast::ConsoleStatement> for ConsoleStatement<'a> { AstConsoleFunction::Assert(expression) => ConsoleFunction::Assert(Cell::new( <&Expression<'a>>::from_ast(scope, expression, Some(Type::Boolean.into()))?, )), - AstConsoleFunction::Debug(formatted_string) => { - ConsoleFunction::Debug(FormatString::from_ast(scope, formatted_string, None)?) - } - AstConsoleFunction::Error(formatted_string) => { - ConsoleFunction::Error(FormatString::from_ast(scope, formatted_string, None)?) - } - AstConsoleFunction::Log(formatted_string) => { - ConsoleFunction::Log(FormatString::from_ast(scope, formatted_string, None)?) - } + AstConsoleFunction::Debug(args) => ConsoleFunction::Debug(ConsoleArgs::from_ast(scope, args, None)?), + AstConsoleFunction::Error(args) => ConsoleFunction::Error(ConsoleArgs::from_ast(scope, args, None)?), + AstConsoleFunction::Log(args) => ConsoleFunction::Log(ConsoleArgs::from_ast(scope, args, None)?), }, }) } @@ -122,9 +117,9 @@ impl<'a> Into for &ConsoleStatement<'a> { leo_ast::ConsoleStatement { function: match &self.function { Assert(e) => AstConsoleFunction::Assert(e.get().into()), - Debug(formatted_string) => AstConsoleFunction::Debug(formatted_string.into()), - Error(formatted_string) => AstConsoleFunction::Error(formatted_string.into()), - Log(formatted_string) => AstConsoleFunction::Log(formatted_string.into()), + Debug(args) => AstConsoleFunction::Debug(args.into()), + Error(args) => AstConsoleFunction::Error(args.into()), + Log(args) => AstConsoleFunction::Log(args.into()), }, span: self.span.clone().unwrap_or_default(), } diff --git a/asg/tests/fail/console/log_parameter_fail_empty.leo b/asg/tests/fail/console/log_parameter_fail_empty.leo deleted file mode 100644 index 81b42c0919..0000000000 --- a/asg/tests/fail/console/log_parameter_fail_empty.leo +++ /dev/null @@ -1,3 +0,0 @@ -function main() { - console.log("{}"); -} \ No newline at end of file diff --git a/asg/tests/fail/console/log_parameter_fail_none.leo b/asg/tests/fail/console/log_parameter_fail_none.leo deleted file mode 100644 index c92fdfbb2d..0000000000 --- a/asg/tests/fail/console/log_parameter_fail_none.leo +++ /dev/null @@ -1,3 +0,0 @@ -function main() { - console.log("", 1u32); -} \ No newline at end of file diff --git a/asg/tests/fail/console/mod.rs b/asg/tests/fail/console/mod.rs index 9f967460f3..d825a4db05 100644 --- a/asg/tests/fail/console/mod.rs +++ b/asg/tests/fail/console/mod.rs @@ -27,15 +27,3 @@ fn test_log_parameter_fail_unknown() { let program_string = include_str!("log_parameter_fail_unknown.leo"); load_asg(program_string).err().unwrap(); } - -#[test] -fn test_log_parameter_fail_empty() { - let program_string = include_str!("log_parameter_fail_empty.leo"); - load_asg(program_string).err().unwrap(); -} - -#[test] -fn test_log_parameter_fail_none() { - let program_string = include_str!("log_parameter_fail_empty.leo"); - load_asg(program_string).err().unwrap(); -} diff --git a/ast/src/errors/reducer.rs b/ast/src/errors/reducer.rs index e8b582af85..9655c47bde 100644 --- a/ast/src/errors/reducer.rs +++ b/ast/src/errors/reducer.rs @@ -35,6 +35,13 @@ impl ReducerError { ReducerError::Error(FormattedError::new_from_span(message, span)) } + pub fn empty_string(span: &Span) -> Self { + let message = + "Cannot constrcut an empty string: it has the type of [char; 0] which is not possible.".to_string(); + + Self::new_from_span(message, span) + } + pub fn impossible_console_assert_call(span: &Span) -> Self { let message = "Console::Assert cannot be matched here, its handled in another case.".to_string(); diff --git a/ast/src/reducer/canonicalization.rs b/ast/src/reducer/canonicalization.rs index c6355fc7b2..9ca30d2aa2 100644 --- a/ast/src/reducer/canonicalization.rs +++ b/ast/src/reducer/canonicalization.rs @@ -391,23 +391,23 @@ impl Canonicalizer { ConsoleFunction::Assert(expression) => { ConsoleFunction::Assert(self.canonicalize_expression(expression)) } - ConsoleFunction::Debug(format) | ConsoleFunction::Error(format) | ConsoleFunction::Log(format) => { - let parameters = format + ConsoleFunction::Debug(args) | ConsoleFunction::Error(args) | ConsoleFunction::Log(args) => { + let parameters = args .parameters .iter() .map(|parameter| self.canonicalize_expression(parameter)) .collect(); - let formatted = FormatString { - parts: format.parts.clone(), + let console_args = ConsoleArgs { + string: args.string.clone(), parameters, - span: format.span.clone(), + span: args.span.clone(), }; match &console_function_call.function { - ConsoleFunction::Debug(_) => ConsoleFunction::Debug(formatted), - ConsoleFunction::Error(_) => ConsoleFunction::Error(formatted), - ConsoleFunction::Log(_) => ConsoleFunction::Log(formatted), + ConsoleFunction::Debug(_) => ConsoleFunction::Debug(console_args), + ConsoleFunction::Error(_) => ConsoleFunction::Error(console_args), + ConsoleFunction::Log(_) => ConsoleFunction::Log(console_args), _ => unimplemented!(), // impossible } } @@ -493,6 +493,10 @@ impl ReconstructingReducer for Canonicalizer { } fn reduce_string(&mut self, string: &[Char], span: &Span) -> Result { + if string.is_empty() { + return Err(ReducerError::empty_string(span)); + } + let mut elements = Vec::new(); let mut col_adder = 0; for (index, character) in string.iter().enumerate() { diff --git a/ast/src/reducer/reconstructing_director.rs b/ast/src/reducer/reconstructing_director.rs index 74fbf76a94..80a61e0476 100644 --- a/ast/src/reducer/reconstructing_director.rs +++ b/ast/src/reducer/reconstructing_director.rs @@ -390,23 +390,23 @@ impl ReconstructingDirector { ) -> Result { let function = match &console_function_call.function { ConsoleFunction::Assert(expression) => ConsoleFunction::Assert(self.reduce_expression(expression)?), - ConsoleFunction::Debug(format) | ConsoleFunction::Error(format) | ConsoleFunction::Log(format) => { + ConsoleFunction::Debug(args) | ConsoleFunction::Error(args) | ConsoleFunction::Log(args) => { let mut parameters = vec![]; - for parameter in format.parameters.iter() { + for parameter in args.parameters.iter() { parameters.push(self.reduce_expression(parameter)?); } - let formatted = FormatString { - parts: format.parts.clone(), + let formatted = ConsoleArgs { + string: args.string.clone(), parameters, - span: format.span.clone(), + span: args.span.clone(), }; match &console_function_call.function { ConsoleFunction::Debug(_) => ConsoleFunction::Debug(formatted), ConsoleFunction::Error(_) => ConsoleFunction::Error(formatted), ConsoleFunction::Log(_) => ConsoleFunction::Log(formatted), - _ => return Err(ReducerError::impossible_console_assert_call(&format.span)), + _ => return Err(ReducerError::impossible_console_assert_call(&args.span)), } } }; diff --git a/ast/src/statements/console/console_args.rs b/ast/src/statements/console/console_args.rs new file mode 100644 index 0000000000..f5d8977dc3 --- /dev/null +++ b/ast/src/statements/console/console_args.rs @@ -0,0 +1,96 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{Char, Expression, Node, Span}; + +use serde::{Deserialize, Serialize}; +use std::fmt; +// use tendril::StrTendril; + +// #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +// pub enum FormatStringPart { +// Const(#[serde(with = "crate::common::tendril_json")] StrTendril), +// Container, +// } + +// impl FormatStringPart { +// pub fn from_string(string: Vec) -> Vec { +// let mut parts = Vec::new(); +// let mut in_container = false; +// let mut substring = String::new(); +// for (_, character) in string.iter().enumerate() { +// match character { +// Char::Scalar(scalar) => match scalar { +// '{' if !in_container => { +// parts.push(FormatStringPart::Const(substring.clone().into())); +// substring.clear(); +// in_container = true; +// } +// '}' if in_container => { +// in_container = false; +// parts.push(FormatStringPart::Container); +// } +// _ if in_container => { +// in_container = false; +// } +// _ => substring.push(*scalar), +// }, +// Char::NonScalar(non_scalar) => { +// substring.push_str(format!("\\u{{{:x}}}", non_scalar).as_str()); +// in_container = false; +// } +// } +// } + +// if !substring.is_empty() { +// parts.push(FormatStringPart::Const(substring.into())); +// } + +// parts +// } +// } + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct ConsoleArgs { + pub string: Vec, + pub parameters: Vec, + pub span: Span, +} + +impl fmt::Display for ConsoleArgs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "\"{}\", {}", + self.string.iter().map(|x| x.to_string()).collect::>().join(""), + self.parameters + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(",") + ) + } +} + +impl Node for ConsoleArgs { + fn span(&self) -> &Span { + &self.span + } + + fn set_span(&mut self, span: Span) { + self.span = span; + } +} diff --git a/ast/src/statements/console/console_function.rs b/ast/src/statements/console/console_function.rs index 17a19931cb..5919980af6 100644 --- a/ast/src/statements/console/console_function.rs +++ b/ast/src/statements/console/console_function.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{Expression, FormatString, Node, Span}; +use crate::{ConsoleArgs, Expression, Node, Span}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -22,9 +22,9 @@ use std::fmt; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub enum ConsoleFunction { Assert(Expression), - Debug(FormatString), - Error(FormatString), - Log(FormatString), + Debug(ConsoleArgs), + Error(ConsoleArgs), + Log(ConsoleArgs), } impl fmt::Display for ConsoleFunction { diff --git a/ast/src/statements/console/formatted_string.rs b/ast/src/statements/console/formatted_string.rs deleted file mode 100644 index 641362b35a..0000000000 --- a/ast/src/statements/console/formatted_string.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use crate::{Char, Expression, Node, Span}; - -use serde::{Deserialize, Serialize}; -use std::fmt; -use tendril::StrTendril; - -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub enum FormatStringPart { - Const(#[serde(with = "crate::common::tendril_json")] StrTendril), - Container, -} - -impl FormatStringPart { - pub fn from_string(string: Vec) -> Vec { - let mut parts = Vec::new(); - let mut in_container = false; - let mut substring = String::new(); - for (_, character) in string.iter().enumerate() { - match character { - Char::Scalar(scalar) => match scalar { - '{' if !in_container => { - parts.push(FormatStringPart::Const(substring.clone().into())); - substring.clear(); - in_container = true; - } - '}' if in_container => { - in_container = false; - parts.push(FormatStringPart::Container); - } - _ if in_container => { - in_container = false; - } - _ => substring.push(*scalar), - }, - Char::NonScalar(non_scalar) => { - substring.push_str(format!("\\u{{{:x}}}", non_scalar).as_str()); - in_container = false; - } - } - } - - if !substring.is_empty() { - parts.push(FormatStringPart::Const(substring.into())); - } - - parts - } -} - -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct FormatString { - pub parts: Vec, - pub parameters: Vec, - pub span: Span, -} - -impl fmt::Display for FormatString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "\"{}\", {}", - self.parts - .iter() - .map(|x| match x { - FormatStringPart::Const(x) => x.to_string(), - FormatStringPart::Container => "{}".to_string(), - }) - .collect::>() - .join(""), - self.parameters - .iter() - .map(|p| p.to_string()) - .collect::>() - .join(",") - ) - } -} - -impl Node for FormatString { - fn span(&self) -> &Span { - &self.span - } - - fn set_span(&mut self, span: Span) { - self.span = span; - } -} diff --git a/ast/src/statements/console/mod.rs b/ast/src/statements/console/mod.rs index da4f604aed..16c2d98a40 100644 --- a/ast/src/statements/console/mod.rs +++ b/ast/src/statements/console/mod.rs @@ -17,11 +17,8 @@ pub mod console_function; pub use console_function::*; +pub mod console_args; +pub use console_args::*; + pub mod console_statement; pub use console_statement::*; - -pub mod formatted_container; -pub use formatted_container::*; - -pub mod formatted_string; -pub use formatted_string::*; diff --git a/compiler/src/console/format.rs b/compiler/src/console/format.rs index 7372fa69d2..dd936223fc 100644 --- a/compiler/src/console/format.rs +++ b/compiler/src/console/format.rs @@ -17,8 +17,7 @@ //! Evaluates a formatted string in a compiled Leo program. use crate::{errors::ConsoleError, program::ConstrainedProgram, GroupType}; -use leo_asg::FormatString; -use leo_ast::FormatStringPart; +use leo_asg::{CharValue, ConsoleArgs}; use snarkvm_fields::PrimeField; use snarkvm_r1cs::ConstraintSystem; @@ -26,35 +25,65 @@ impl<'a, F: PrimeField, G: GroupType> ConstrainedProgram<'a, F, G> { pub fn format>( &mut self, cs: &mut CS, - formatted: &FormatString<'a>, + args: &ConsoleArgs<'a>, ) -> Result { - // Check that containers and parameters match - let container_count = formatted - .parts - .iter() - .filter(|x| matches!(x, FormatStringPart::Container)) - .count(); - if container_count != formatted.parameters.len() { - return Err(ConsoleError::length( - container_count, - formatted.parameters.len(), - &formatted.span, - )); - } - - let mut executed_containers = Vec::with_capacity(formatted.parameters.len()); - for parameter in formatted.parameters.iter() { - executed_containers.push(self.enforce_expression(cs, parameter.get())?.to_string()); - } - - let mut out = vec![]; - let mut parameters = executed_containers.iter(); - for part in formatted.parts.iter() { - match part { - FormatStringPart::Const(c) => out.push(c.to_string()), - FormatStringPart::Container => out.push(parameters.next().unwrap().to_string()), + let mut out = Vec::new(); + let mut in_container = false; + let mut substring = String::new(); + let mut arg_index = 0; + let mut escape_right_bracket = false; + for (index, character) in args.string.iter().enumerate() { + match character { + _ if escape_right_bracket => { + escape_right_bracket = false; + continue; + } + CharValue::Scalar(scalar) => match scalar { + '{' if !in_container => { + out.push(substring.clone()); + substring.clear(); + in_container = true; + } + '{' if in_container => { + substring.push('{'); + in_container = false; + } + '}' if in_container => { + in_container = false; + let parameter = match args.parameters.get(arg_index) { + Some(index) => index, + None => return Err(ConsoleError::length(arg_index + 1, args.parameters.len(), &args.span)), + }; + out.push(self.enforce_expression(cs, parameter.get())?.to_string()); + arg_index += 1; + } + '}' if !in_container => { + if let Some(CharValue::Scalar(next)) = args.string.get(index + 1) { + if *next == '}' { + substring.push('}'); + escape_right_bracket = true; + } else { + return Err(ConsoleError::expected_escaped_right_brace(&args.span)); + } + } + } + _ if in_container => { + return Err(ConsoleError::expected_left_or_right_brace(&args.span)); + } + _ => substring.push(*scalar), + }, + CharValue::NonScalar(non_scalar) => { + substring.push_str(format!("\\u{{{:x}}}", non_scalar).as_str()); + in_container = false; + } } } + out.push(substring); + + // Check that containers and parameters match + if arg_index != args.parameters.len() { + return Err(ConsoleError::length(arg_index, args.parameters.len(), &args.span)); + } Ok(out.join("")) } diff --git a/compiler/src/errors/console.rs b/compiler/src/errors/console.rs index 62ee02446a..76d4538f1c 100644 --- a/compiler/src/errors/console.rs +++ b/compiler/src/errors/console.rs @@ -33,6 +33,18 @@ impl ConsoleError { ConsoleError::Error(FormattedError::new_from_span(message, span)) } + pub fn expected_left_or_right_brace(span: &Span) -> Self { + let message = "Formatter given a {. Expected a { or } after".to_string(); + + Self::new_from_span(message, span) + } + + pub fn expected_escaped_right_brace(span: &Span) -> Self { + let message = "Formatter given a }. Expected a container {} or }}".to_string(); + + Self::new_from_span(message, span) + } + pub fn length(containers: usize, parameters: usize, span: &Span) -> Self { let message = format!( "Formatter given {} containers and found {} parameters", diff --git a/compiler/src/phases/reducing_director.rs b/compiler/src/phases/reducing_director.rs index 5063774993..ff432e3f27 100644 --- a/compiler/src/phases/reducing_director.rs +++ b/compiler/src/phases/reducing_director.rs @@ -76,12 +76,12 @@ use leo_ast::{ CircuitStaticFunctionAccessExpression, CombinerError, ConditionalStatement as AstConditionalStatement, + ConsoleArgs as AstConsoleArgs, ConsoleFunction as AstConsoleFunction, ConsoleStatement as AstConsoleStatement, DefinitionStatement as AstDefinitionStatement, Expression as AstExpression, ExpressionStatement as AstExpressionStatement, - FormatString, Function as AstFunction, GroupTuple, GroupValue as AstGroupValue, @@ -597,25 +597,27 @@ impl CombineAstAsgDirector { (AstConsoleFunction::Assert(ast_expression), AsgConsoleFunction::Assert(asg_expression)) => { AstConsoleFunction::Assert(self.reduce_expression(&ast_expression, asg_expression.get())?) } - (AstConsoleFunction::Debug(ast_format), AsgConsoleFunction::Debug(asg_format)) - | (AstConsoleFunction::Error(ast_format), AsgConsoleFunction::Error(asg_format)) - | (AstConsoleFunction::Log(ast_format), AsgConsoleFunction::Log(asg_format)) => { + (AstConsoleFunction::Debug(ast_console_args), AsgConsoleFunction::Debug(asg_format)) + | (AstConsoleFunction::Error(ast_console_args), AsgConsoleFunction::Error(asg_format)) + | (AstConsoleFunction::Log(ast_console_args), AsgConsoleFunction::Log(asg_format)) => { let mut parameters = vec![]; - for (ast_parameter, asg_parameter) in ast_format.parameters.iter().zip(asg_format.parameters.iter()) { + for (ast_parameter, asg_parameter) in + ast_console_args.parameters.iter().zip(asg_format.parameters.iter()) + { parameters.push(self.reduce_expression(&ast_parameter, asg_parameter.get())?); } - let formatted = FormatString { - parts: ast_format.parts.clone(), + let args = AstConsoleArgs { + string: ast_console_args.string.clone(), parameters, - span: ast_format.span.clone(), + span: ast_console_args.span.clone(), }; match &ast.function { - AstConsoleFunction::Debug(_) => AstConsoleFunction::Debug(formatted), - AstConsoleFunction::Error(_) => AstConsoleFunction::Error(formatted), - AstConsoleFunction::Log(_) => AstConsoleFunction::Log(formatted), - _ => return Err(ReducerError::impossible_console_assert_call(&ast_format.span)), + AstConsoleFunction::Debug(_) => AstConsoleFunction::Debug(args), + AstConsoleFunction::Error(_) => AstConsoleFunction::Error(args), + AstConsoleFunction::Log(_) => AstConsoleFunction::Log(args), + _ => return Err(ReducerError::impossible_console_assert_call(&ast_console_args.span)), } } _ => ast.function.clone(), diff --git a/grammar/README.md b/grammar/README.md index 0086f21350..6491e949ff 100644 Binary files a/grammar/README.md and b/grammar/README.md differ diff --git a/grammar/abnf-grammar.txt b/grammar/abnf-grammar.txt index 29e925c70a..df8c9c8338 100644 --- a/grammar/abnf-grammar.txt +++ b/grammar/abnf-grammar.txt @@ -559,7 +559,7 @@ unicode-character-escape = %s"\u{" 1*6hexadecimal-digit "}" ; because string literals denote character arrays, ; and arrays must not be empty. -string-literal = double-quote 1*string-literal-element double-quote +string-literal = double-quote *string-literal-element double-quote string-literal-element = not-double-quote-or-backslash / simple-character-escape diff --git a/parser/README.md b/parser/README.md new file mode 100644 index 0000000000..c0ff343922 --- /dev/null +++ b/parser/README.md @@ -0,0 +1,5 @@ +# leo-parser + +[![Crates.io](https://img.shields.io/crates/v/leo-parser.svg?color=neon)](https://crates.io/crates/leo-parser) +[![Authors](https://img.shields.io/badge/authors-Aleo-orange.svg)](../AUTHORS) +[![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE.md) diff --git a/parser/src/parser/statement.rs b/parser/src/parser/statement.rs index eff1b31e31..f4ec7ea566 100644 --- a/parser/src/parser/statement.rs +++ b/parser/src/parser/statement.rs @@ -232,9 +232,9 @@ impl ParserContext { } /// - /// Returns a [`FormatString`] AST node if the next tokens represent a formatted string. + /// Returns a [`ConsoleArgs`] AST node if the next tokens represent a formatted string. /// - pub fn parse_formatted_string(&mut self) -> SyntaxResult { + pub fn parse_console_args(&mut self) -> SyntaxResult { let start_span; let string = match self.expect_any()? { SpannedToken { @@ -247,7 +247,7 @@ impl ParserContext { SpannedToken { token, span } => return Err(SyntaxError::unexpected_str(&token, "formatted string", &span)), }; - let parts = FormatStringPart::from_string(string); + // let parts = FormatStringPart::from_string(string); let mut parameters = Vec::new(); while self.eat(Token::Comma).is_some() { @@ -255,8 +255,8 @@ impl ParserContext { parameters.push(param); } - Ok(FormatString { - parts, + Ok(ConsoleArgs { + string, span: &start_span + parameters.last().map(|x| x.span()).unwrap_or(&start_span), parameters, }) @@ -275,9 +275,9 @@ impl ParserContext { let expr = self.parse_expression()?; ConsoleFunction::Assert(expr) } - "debug" => ConsoleFunction::Debug(self.parse_formatted_string()?), - "error" => ConsoleFunction::Error(self.parse_formatted_string()?), - "log" => ConsoleFunction::Log(self.parse_formatted_string()?), + "debug" => ConsoleFunction::Debug(self.parse_console_args()?), + "error" => ConsoleFunction::Error(self.parse_console_args()?), + "log" => ConsoleFunction::Log(self.parse_console_args()?), x => { return Err(SyntaxError::unexpected_ident( &x, diff --git a/parser/src/tokenizer/lexer.rs b/parser/src/tokenizer/lexer.rs index 9c3b9f2be0..cf9b452e9b 100644 --- a/parser/src/tokenizer/lexer.rs +++ b/parser/src/tokenizer/lexer.rs @@ -280,7 +280,7 @@ impl Token { } } - if i == input.len() || i == 1 || !end { + if i == input.len() || !end { return (0, None); } diff --git a/tests/expectations/compiler/compiler/console/log_parameter_fail_empty.leo.out b/tests/expectations/compiler/compiler/console/log_parameter_fail_empty.leo.out index b3e4e8bbfe..6ebd98b417 100644 --- a/tests/expectations/compiler/compiler/console/log_parameter_fail_empty.leo.out +++ b/tests/expectations/compiler/compiler/console/log_parameter_fail_empty.leo.out @@ -2,4 +2,4 @@ namespace: Compile expectation: Fail outputs: - - " --> compiler-test:4:17\n |\n 4 | console.log(\"{}\");\n | ^^^^\n |\n = function call expected 2 arguments, got 1" + - " --> compiler-test:4:17\n |\n 4 | console.log(\"{}\");\n | ^^^^\n |\n = Formatter given 1 containers and found 0 parameters" diff --git a/tests/expectations/compiler/compiler/console/log_parameter_fail_none.leo.out b/tests/expectations/compiler/compiler/console/log_parameter_fail_none.leo.out index 56c7a9e3b8..f7007ccf5f 100644 --- a/tests/expectations/compiler/compiler/console/log_parameter_fail_none.leo.out +++ b/tests/expectations/compiler/compiler/console/log_parameter_fail_none.leo.out @@ -2,4 +2,4 @@ namespace: Compile expectation: Fail outputs: - - " --> compiler-test:4:17\n |\n 4 | console.log(\"\", 1u32);\n | ^\n |\n = unexpected token: '\"'" + - " --> compiler-test:4:17\n |\n 4 | console.log(\"\", 1u32);\n | ^^^^^^^^\n |\n = Formatter given 0 containers and found 1 parameters" diff --git a/tests/expectations/parser/parser/expression/literal/string.leo.out b/tests/expectations/parser/parser/expression/literal/string.leo.out index 2eab41078d..c6b41bc0d5 100644 --- a/tests/expectations/parser/parser/expression/literal/string.leo.out +++ b/tests/expectations/parser/parser/expression/literal/string.leo.out @@ -2,6 +2,7 @@ namespace: Token expectation: Pass outputs: + - "'\"\"' @ 1:1-3" - "'\"string\"' @ 1:1-9" - "'\"another { } string\"' @ 1:1-21" - "'\"{ ] [ ; a\"' @ 1:1-12" diff --git a/tests/expectations/parser/parser/expression/literal/string_fail.leo.out b/tests/expectations/parser/parser/expression/literal/string_fail.leo.out index d97d2cda74..891ec74b3c 100644 --- a/tests/expectations/parser/parser/expression/literal/string_fail.leo.out +++ b/tests/expectations/parser/parser/expression/literal/string_fail.leo.out @@ -2,7 +2,6 @@ namespace: Token expectation: Fail outputs: - - " --> test:1:1\n |\n 1 | \"\"\n | ^\n |\n = unexpected token: '\"'" - " --> test:1:1\n |\n 1 | \"Hello world!\n | ^\n |\n = unexpected token: '\"'" - " --> test:1:1\n |\n 1 | \"\\\"\n | ^\n |\n = unexpected token: '\"'" - " --> test:1:1\n |\n 1 | \"\\l\"\n | ^\n |\n = unexpected token: '\"'" diff --git a/tests/expectations/parser/parser/statement/console.leo.out b/tests/expectations/parser/parser/statement/console.leo.out index a70bf45485..2ed38532c9 100644 --- a/tests/expectations/parser/parser/statement/console.leo.out +++ b/tests/expectations/parser/parser/statement/console.leo.out @@ -16,9 +16,9 @@ outputs: - Console: function: Error: - parts: - - Const: "" - - Container + string: + - Scalar: 123 + - Scalar: 125 parameters: - Identifier: "{\"name\":\"x\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":21,\\\"col_stop\\\":22,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.error(\\\\\\\"{}\\\\\\\", x);\\\"}\"}" span: @@ -38,11 +38,11 @@ outputs: - Console: function: Error: - parts: - - Const: "" - - Container - - Const: "" - - Container + string: + - Scalar: 123 + - Scalar: 125 + - Scalar: 123 + - Scalar: 125 parameters: - Identifier: "{\"name\":\"x\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":23,\\\"col_stop\\\":24,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.error(\\\\\\\"{}{}\\\\\\\", x, y);\\\"}\"}" - Identifier: "{\"name\":\"y\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":26,\\\"col_stop\\\":27,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.error(\\\\\\\"{}{}\\\\\\\", x, y);\\\"}\"}" @@ -63,8 +63,8 @@ outputs: - Console: function: Error: - parts: - - Const: x + string: + - Scalar: 120 parameters: [] span: line_start: 1 @@ -83,9 +83,9 @@ outputs: - Console: function: Debug: - parts: - - Const: "" - - Container + string: + - Scalar: 123 + - Scalar: 125 parameters: - Identifier: "{\"name\":\"x\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":21,\\\"col_stop\\\":22,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.debug(\\\\\\\"{}\\\\\\\", x);\\\"}\"}" span: @@ -105,11 +105,11 @@ outputs: - Console: function: Debug: - parts: - - Const: "" - - Container - - Const: "" - - Container + string: + - Scalar: 123 + - Scalar: 125 + - Scalar: 123 + - Scalar: 125 parameters: - Identifier: "{\"name\":\"x\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":23,\\\"col_stop\\\":24,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.debug(\\\\\\\"{}{}\\\\\\\", x, y);\\\"}\"}" - Identifier: "{\"name\":\"y\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":26,\\\"col_stop\\\":27,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.debug(\\\\\\\"{}{}\\\\\\\", x, y);\\\"}\"}" @@ -130,8 +130,8 @@ outputs: - Console: function: Debug: - parts: - - Const: x + string: + - Scalar: 120 parameters: [] span: line_start: 1 @@ -150,9 +150,9 @@ outputs: - Console: function: Log: - parts: - - Const: "" - - Container + string: + - Scalar: 123 + - Scalar: 125 parameters: - Identifier: "{\"name\":\"x\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":19,\\\"col_stop\\\":20,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.log(\\\\\\\"{}\\\\\\\", x);\\\"}\"}" span: @@ -172,11 +172,11 @@ outputs: - Console: function: Log: - parts: - - Const: "" - - Container - - Const: "" - - Container + string: + - Scalar: 123 + - Scalar: 125 + - Scalar: 123 + - Scalar: 125 parameters: - Identifier: "{\"name\":\"x\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":21,\\\"col_stop\\\":22,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.log(\\\\\\\"{}{}\\\\\\\", x, y);\\\"}\"}" - Identifier: "{\"name\":\"y\",\"span\":\"{\\\"line_start\\\":1,\\\"line_stop\\\":1,\\\"col_start\\\":24,\\\"col_stop\\\":25,\\\"path\\\":\\\"test\\\",\\\"content\\\":\\\"console.log(\\\\\\\"{}{}\\\\\\\", x, y);\\\"}\"}" @@ -197,8 +197,8 @@ outputs: - Console: function: Log: - parts: - - Const: x + string: + - Scalar: 120 parameters: [] span: line_start: 1 diff --git a/tests/parser/expression/literal/string.leo b/tests/parser/expression/literal/string.leo index 1b397dfa83..644ad63f3c 100644 --- a/tests/parser/expression/literal/string.leo +++ b/tests/parser/expression/literal/string.leo @@ -3,6 +3,8 @@ namespace: Token expectation: Pass */ +"" + "string" "another { } string" diff --git a/tests/parser/expression/literal/string_fail.leo b/tests/parser/expression/literal/string_fail.leo index 3015bf53c3..de90e88599 100644 --- a/tests/parser/expression/literal/string_fail.leo +++ b/tests/parser/expression/literal/string_fail.leo @@ -3,8 +3,6 @@ namespace: Token expectation: Fail */ -"" - "Hello world! "\"