From b49d5005b1e343083aab7e3362a777330d809c37 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 15:46:27 +0000 Subject: [PATCH 01/12] chore: move assertion parsing into submodule --- compiler/noirc_frontend/src/parser/parser.rs | 252 +++--------------- .../src/parser/parser/assertion.rs | 219 +++++++++++++++ 2 files changed, 257 insertions(+), 214 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/assertion.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 1cb81e26a0a..df12534f421 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -37,11 +37,11 @@ use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Attribute, Attributes, Keyword, SecondaryAttribute, Token, TokenKind}; use crate::{ - BinaryOp, BinaryOpKind, BlockExpression, ConstrainKind, ConstrainStatement, Distinctness, - ForLoopStatement, ForRange, FunctionDefinition, FunctionReturnType, FunctionVisibility, Ident, - IfExpression, InfixExpression, LValue, Lambda, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTraitImpl, NoirTypeAlias, Param, Path, PathKind, Pattern, Recoverable, Statement, - TraitBound, TraitImplItem, TraitItem, TypeImpl, UnaryOp, UnresolvedTraitConstraint, + BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, + FunctionDefinition, FunctionReturnType, FunctionVisibility, Ident, IfExpression, + InfixExpression, LValue, Lambda, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, + NoirTypeAlias, Param, Path, PathKind, Pattern, Recoverable, Statement, TraitBound, + TraitImplItem, TraitItem, TypeImpl, UnaryOp, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; @@ -49,6 +49,8 @@ use chumsky::prelude::*; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; +mod assertion; + /// Entry function for the parser - also handles lexing internally. /// /// Given a source_program string, return the ParsedModule Ast representation @@ -792,9 +794,9 @@ where { recursive(|statement| { choice(( - constrain(expr_parser.clone()), - assertion(expr_parser.clone()), - assertion_eq(expr_parser.clone()), + assertion::constrain(expr_parser.clone()), + assertion::assertion(expr_parser.clone()), + assertion::assertion_eq(expr_parser.clone()), declaration(expr_parser.clone()), assignment(expr_parser.clone()), for_loop(expr_no_constructors, statement), @@ -808,64 +810,6 @@ fn fresh_statement() -> impl NoirParser { statement(expression(), expression_no_constructors(expression())) } -fn constrain<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - ignore_then_commit( - keyword(Keyword::Constrain).labelled(ParsingRuleLabel::Statement), - expr_parser, - ) - .map(|expr| StatementKind::Constrain(ConstrainStatement(expr, None, ConstrainKind::Constrain))) - .validate(|expr, span, emit| { - emit(ParserError::with_reason(ParserErrorReason::ConstrainDeprecated, span)); - expr - }) -} - -fn assertion<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let argument_parser = - expr_parser.separated_by(just(Token::Comma)).allow_trailing().at_least(1).at_most(2); - - ignore_then_commit(keyword(Keyword::Assert), parenthesized(argument_parser)) - .labelled(ParsingRuleLabel::Statement) - .validate(|expressions, span, _| { - let condition = expressions.first().unwrap_or(&Expression::error(span)).clone(); - let message = expressions.get(1).cloned(); - StatementKind::Constrain(ConstrainStatement(condition, message, ConstrainKind::Assert)) - }) -} - -fn assertion_eq<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let argument_parser = - expr_parser.separated_by(just(Token::Comma)).allow_trailing().at_least(2).at_most(3); - - ignore_then_commit(keyword(Keyword::AssertEq), parenthesized(argument_parser)) - .labelled(ParsingRuleLabel::Statement) - .validate(|exprs: Vec, span, _| { - let predicate = Expression::new( - ExpressionKind::Infix(Box::new(InfixExpression { - lhs: exprs.first().unwrap_or(&Expression::error(span)).clone(), - rhs: exprs.get(1).unwrap_or(&Expression::error(span)).clone(), - operator: Spanned::from(span, BinaryOpKind::Equal), - })), - span, - ); - let message = exprs.get(2).cloned(); - StatementKind::Constrain(ConstrainStatement( - predicate, - message, - ConstrainKind::AssertEq, - )) - }) -} - fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, @@ -1671,13 +1615,19 @@ fn literal() -> impl NoirParser { } #[cfg(test)] -mod test { +mod parser_test_helpers { + use chumsky::primitive::just; + use chumsky::Parser; + use iter_extended::vecmap; use noirc_errors::CustomDiagnostic; - use super::*; - use crate::{ArrayLiteral, Literal}; + use crate::{ + lexer::Lexer, + parser::{force, NoirParser}, + token::Token, + }; - fn parse_with(parser: P, program: &str) -> Result> + pub(crate) fn parse_with(parser: P, program: &str) -> Result> where P: NoirParser, { @@ -1691,7 +1641,10 @@ mod test { .map_err(|errors| vecmap(errors, Into::into)) } - fn parse_recover(parser: P, program: &str) -> (Option, Vec) + pub(crate) fn parse_recover( + parser: P, + program: &str, + ) -> (Option, Vec) where P: NoirParser, { @@ -1704,7 +1657,7 @@ mod test { (opt, errors) } - fn parse_all(parser: P, programs: Vec<&str>) -> Vec + pub(crate) fn parse_all(parser: P, programs: Vec<&str>) -> Vec where P: NoirParser, { @@ -1720,7 +1673,7 @@ mod test { }) } - fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec + pub(crate) fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec where P: NoirParser, T: std::fmt::Display, @@ -1748,13 +1701,13 @@ mod test { } #[derive(Copy, Clone)] - struct Case { - source: &'static str, - errors: usize, - expect: &'static str, + pub(crate) struct Case { + pub(crate) source: &'static str, + pub(crate) errors: usize, + pub(crate) expect: &'static str, } - fn check_cases_with_errors(cases: &[Case], parser: P) + pub(crate) fn check_cases_with_errors(cases: &[Case], parser: P) where P: NoirParser + Clone, T: std::fmt::Display, @@ -1791,6 +1744,13 @@ mod test { assert_eq!(vecmap(&results, |t| t.0.clone()), vecmap(&results, |t| t.1.clone()),); } +} + +#[cfg(test)] +mod test { + use super::parser_test_helpers::*; + use super::*; + use crate::{ArrayLiteral, Literal}; #[test] fn regression_skip_comment() { @@ -1966,142 +1926,6 @@ mod test { } } - /// Deprecated constrain usage test - #[test] - fn parse_constrain() { - let errors = parse_with(constrain(expression()), "constrain x == y").unwrap_err(); - assert_eq!(errors.len(), 1); - assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); - - // Currently we disallow constrain statements where the outer infix operator - // produces a value. This would require an implicit `==` which - // may not be intuitive to the user. - // - // If this is deemed useful, one would either apply a transformation - // or interpret it with an `==` in the evaluator - let disallowed_operators = vec![ - BinaryOpKind::And, - BinaryOpKind::Subtract, - BinaryOpKind::Divide, - BinaryOpKind::Multiply, - BinaryOpKind::Or, - ]; - - for operator in disallowed_operators { - let src = format!("constrain x {} y;", operator.as_string()); - let errors = parse_with(constrain(expression()), &src).unwrap_err(); - assert_eq!(errors.len(), 2); - assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); - } - - // These are general cases which should always work. - // - // The first case is the most noteworthy. It contains two `==` - // The first (inner) `==` is a predicate which returns 0/1 - // The outer layer is an infix `==` which is - // associated with the Constrain statement - let errors = parse_all_failing( - constrain(expression()), - vec![ - "constrain ((x + y) == k) + z == y", - "constrain (x + !y) == y", - "constrain (x ^ y) == y", - "constrain (x ^ y) == (y + m)", - "constrain x + x ^ x == y | m", - ], - ); - assert_eq!(errors.len(), 5); - assert!(errors - .iter() - .all(|err| { err.is_error() && err.to_string().contains("deprecated") })); - } - - /// This is the standard way to declare an assert statement - #[test] - fn parse_assert() { - parse_with(assertion(expression()), "assert(x == y)").unwrap(); - - // Currently we disallow constrain statements where the outer infix operator - // produces a value. This would require an implicit `==` which - // may not be intuitive to the user. - // - // If this is deemed useful, one would either apply a transformation - // or interpret it with an `==` in the evaluator - let disallowed_operators = vec![ - BinaryOpKind::And, - BinaryOpKind::Subtract, - BinaryOpKind::Divide, - BinaryOpKind::Multiply, - BinaryOpKind::Or, - ]; - - for operator in disallowed_operators { - let src = format!("assert(x {} y);", operator.as_string()); - parse_with(assertion(expression()), &src).unwrap_err(); - } - - // These are general cases which should always work. - // - // The first case is the most noteworthy. It contains two `==` - // The first (inner) `==` is a predicate which returns 0/1 - // The outer layer is an infix `==` which is - // associated with the Constrain statement - parse_all( - assertion(expression()), - vec![ - "assert(((x + y) == k) + z == y)", - "assert((x + !y) == y)", - "assert((x ^ y) == y)", - "assert((x ^ y) == (y + m))", - "assert(x + x ^ x == y | m)", - ], - ); - - match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() - { - StatementKind::Constrain(ConstrainStatement(_, message, _)) => { - let message = message.unwrap(); - match message.kind { - ExpressionKind::Literal(Literal::Str(message_string)) => { - assert_eq!(message_string, "assertion message".to_owned()); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } - - /// This is the standard way to assert that two expressions are equivalent - #[test] - fn parse_assert_eq() { - parse_all( - assertion_eq(expression()), - vec![ - "assert_eq(x, y)", - "assert_eq(((x + y) == k) + z, y)", - "assert_eq(x + !y, y)", - "assert_eq(x ^ y, y)", - "assert_eq(x ^ y, y + m)", - "assert_eq(x + x ^ x, y | m)", - ], - ); - match parse_with(assertion_eq(expression()), "assert_eq(x, y, \"assertion message\")") - .unwrap() - { - StatementKind::Constrain(ConstrainStatement(_, message, _)) => { - let message = message.unwrap(); - match message.kind { - ExpressionKind::Literal(Literal::Str(message_string)) => { - assert_eq!(message_string, "assertion message".to_owned()); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } - #[test] fn parse_let() { // Why is it valid to specify a let declaration as having type u8? diff --git a/compiler/noirc_frontend/src/parser/parser/assertion.rs b/compiler/noirc_frontend/src/parser/parser/assertion.rs new file mode 100644 index 00000000000..dae4464eff6 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/assertion.rs @@ -0,0 +1,219 @@ +use crate::ast::{Expression, ExpressionKind, StatementKind}; +use crate::parser::{ + ignore_then_commit, labels::ParsingRuleLabel, parenthesized, ExprParser, NoirParser, + ParserError, ParserErrorReason, +}; + +use crate::token::{Keyword, Token}; +use crate::{BinaryOpKind, ConstrainKind, ConstrainStatement, InfixExpression, Recoverable}; + +use chumsky::prelude::*; +use noirc_errors::Spanned; + +use super::keyword; + +pub(super) fn constrain<'a, P>(expr_parser: P) -> impl NoirParser + 'a +where + P: ExprParser + 'a, +{ + ignore_then_commit( + keyword(Keyword::Constrain).labelled(ParsingRuleLabel::Statement), + expr_parser, + ) + .map(|expr| StatementKind::Constrain(ConstrainStatement(expr, None, ConstrainKind::Constrain))) + .validate(|expr, span, emit| { + emit(ParserError::with_reason(ParserErrorReason::ConstrainDeprecated, span)); + expr + }) +} + +pub(super) fn assertion<'a, P>(expr_parser: P) -> impl NoirParser + 'a +where + P: ExprParser + 'a, +{ + let argument_parser = + expr_parser.separated_by(just(Token::Comma)).allow_trailing().at_least(1).at_most(2); + + ignore_then_commit(keyword(Keyword::Assert), parenthesized(argument_parser)) + .labelled(ParsingRuleLabel::Statement) + .validate(|expressions, span, _| { + let condition = expressions.first().unwrap_or(&Expression::error(span)).clone(); + let message = expressions.get(1).cloned(); + StatementKind::Constrain(ConstrainStatement(condition, message, ConstrainKind::Assert)) + }) +} + +pub(super) fn assertion_eq<'a, P>(expr_parser: P) -> impl NoirParser + 'a +where + P: ExprParser + 'a, +{ + let argument_parser = + expr_parser.separated_by(just(Token::Comma)).allow_trailing().at_least(2).at_most(3); + + ignore_then_commit(keyword(Keyword::AssertEq), parenthesized(argument_parser)) + .labelled(ParsingRuleLabel::Statement) + .validate(|exprs: Vec, span, _| { + let predicate = Expression::new( + ExpressionKind::Infix(Box::new(InfixExpression { + lhs: exprs.first().unwrap_or(&Expression::error(span)).clone(), + rhs: exprs.get(1).unwrap_or(&Expression::error(span)).clone(), + operator: Spanned::from(span, BinaryOpKind::Equal), + })), + span, + ); + let message = exprs.get(2).cloned(); + StatementKind::Constrain(ConstrainStatement( + predicate, + message, + ConstrainKind::AssertEq, + )) + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + parser::parser::{ + expression, + parser_test_helpers::{parse_all, parse_all_failing, parse_with}, + }, + Literal, + }; + + /// Deprecated constrain usage test + #[test] + fn parse_constrain() { + let errors = parse_with(constrain(expression()), "constrain x == y").unwrap_err(); + assert_eq!(errors.len(), 1); + assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); + + // Currently we disallow constrain statements where the outer infix operator + // produces a value. This would require an implicit `==` which + // may not be intuitive to the user. + // + // If this is deemed useful, one would either apply a transformation + // or interpret it with an `==` in the evaluator + let disallowed_operators = vec![ + BinaryOpKind::And, + BinaryOpKind::Subtract, + BinaryOpKind::Divide, + BinaryOpKind::Multiply, + BinaryOpKind::Or, + ]; + + for operator in disallowed_operators { + let src = format!("constrain x {} y;", operator.as_string()); + let errors = parse_with(constrain(expression()), &src).unwrap_err(); + assert_eq!(errors.len(), 2); + assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); + } + + // These are general cases which should always work. + // + // The first case is the most noteworthy. It contains two `==` + // The first (inner) `==` is a predicate which returns 0/1 + // The outer layer is an infix `==` which is + // associated with the Constrain statement + let errors = parse_all_failing( + constrain(expression()), + vec![ + "constrain ((x + y) == k) + z == y", + "constrain (x + !y) == y", + "constrain (x ^ y) == y", + "constrain (x ^ y) == (y + m)", + "constrain x + x ^ x == y | m", + ], + ); + assert_eq!(errors.len(), 5); + assert!(errors + .iter() + .all(|err| { err.is_error() && err.to_string().contains("deprecated") })); + } + + /// This is the standard way to declare an assert statement + #[test] + fn parse_assert() { + parse_with(assertion(expression()), "assert(x == y)").unwrap(); + + // Currently we disallow constrain statements where the outer infix operator + // produces a value. This would require an implicit `==` which + // may not be intuitive to the user. + // + // If this is deemed useful, one would either apply a transformation + // or interpret it with an `==` in the evaluator + let disallowed_operators = vec![ + BinaryOpKind::And, + BinaryOpKind::Subtract, + BinaryOpKind::Divide, + BinaryOpKind::Multiply, + BinaryOpKind::Or, + ]; + + for operator in disallowed_operators { + let src = format!("assert(x {} y);", operator.as_string()); + parse_with(assertion(expression()), &src).unwrap_err(); + } + + // These are general cases which should always work. + // + // The first case is the most noteworthy. It contains two `==` + // The first (inner) `==` is a predicate which returns 0/1 + // The outer layer is an infix `==` which is + // associated with the Constrain statement + parse_all( + assertion(expression()), + vec![ + "assert(((x + y) == k) + z == y)", + "assert((x + !y) == y)", + "assert((x ^ y) == y)", + "assert((x ^ y) == (y + m))", + "assert(x + x ^ x == y | m)", + ], + ); + + match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() + { + StatementKind::Constrain(ConstrainStatement(_, message, _)) => { + let message = message.unwrap(); + match message.kind { + ExpressionKind::Literal(Literal::Str(message_string)) => { + assert_eq!(message_string, "assertion message".to_owned()); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } + + /// This is the standard way to assert that two expressions are equivalent + #[test] + fn parse_assert_eq() { + parse_all( + assertion_eq(expression()), + vec![ + "assert_eq(x, y)", + "assert_eq(((x + y) == k) + z, y)", + "assert_eq(x + !y, y)", + "assert_eq(x ^ y, y)", + "assert_eq(x ^ y, y + m)", + "assert_eq(x + x ^ x, y | m)", + ], + ); + match parse_with(assertion_eq(expression()), "assert_eq(x, y, \"assertion message\")") + .unwrap() + { + StatementKind::Constrain(ConstrainStatement(_, message, _)) => { + let message = message.unwrap(); + match message.kind { + ExpressionKind::Literal(Literal::Str(message_string)) => { + assert_eq!(message_string, "assertion message".to_owned()); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } +} From 5a8b486df7364af8f517657f06fc35273f24fba5 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 16:18:54 +0000 Subject: [PATCH 02/12] chore: move path parsing into separate module --- compiler/noirc_frontend/src/parser/parser.rs | 71 ++--------------- .../noirc_frontend/src/parser/parser/path.rs | 78 +++++++++++++++++++ 2 files changed, 85 insertions(+), 64 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/path.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index df12534f421..ed3948bb703 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -40,9 +40,9 @@ use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, FunctionDefinition, FunctionReturnType, FunctionVisibility, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, - NoirTypeAlias, Param, Path, PathKind, Pattern, Recoverable, Statement, TraitBound, - TraitImplItem, TraitItem, TypeImpl, UnaryOp, UnresolvedTraitConstraint, - UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, + NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TraitImplItem, + TraitItem, TypeImpl, UnaryOp, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, + UseTreeKind, Visibility, }; use chumsky::prelude::*; @@ -50,6 +50,9 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; mod assertion; +mod path; + +use path::{maybe_empty_path, path}; /// Entry function for the parser - also handles lexing internally. /// @@ -733,27 +736,6 @@ fn token_kind(token_kind: TokenKind) -> impl NoirParser { }) } -fn path() -> impl NoirParser { - let idents = || ident().separated_by(just(Token::DoubleColon)).at_least(1); - let make_path = |kind| move |segments, span| Path { segments, kind, span }; - - let prefix = |key| keyword(key).ignore_then(just(Token::DoubleColon)); - let path_kind = |key, kind| prefix(key).ignore_then(idents()).map_with_span(make_path(kind)); - - choice(( - path_kind(Keyword::Crate, PathKind::Crate), - path_kind(Keyword::Dep, PathKind::Dep), - idents().map_with_span(make_path(PathKind::Plain)), - )) -} - -fn empty_path() -> impl NoirParser { - let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; - let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); - - choice((path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Dep))) -} - fn rename() -> impl NoirParser> { ignore_then_commit(keyword(Keyword::As), ident()).or_not() } @@ -766,7 +748,7 @@ fn use_tree() -> impl NoirParser { }); let list = { - let prefix = path().or(empty_path()).then_ignore(just(Token::DoubleColon)); + let prefix = maybe_empty_path().then_ignore(just(Token::DoubleColon)); let tree = use_tree .separated_by(just(Token::Comma)) .allow_trailing() @@ -2114,45 +2096,6 @@ mod test { parse_with(module_declaration(), "mod 1").unwrap_err(); } - #[test] - fn parse_path() { - let cases = vec![ - ("std", vec!["std"]), - ("std::hash", vec!["std", "hash"]), - ("std::hash::collections", vec!["std", "hash", "collections"]), - ("dep::foo::bar", vec!["foo", "bar"]), - ("crate::std::hash", vec!["std", "hash"]), - ]; - - for (src, expected_segments) in cases { - let path: Path = parse_with(path(), src).unwrap(); - for (segment, expected) in path.segments.into_iter().zip(expected_segments) { - assert_eq!(segment.0.contents, expected); - } - } - - parse_all_failing(path(), vec!["std::", "::std", "std::hash::", "foo::1"]); - } - - #[test] - fn parse_path_kinds() { - let cases = vec![ - ("std", PathKind::Plain), - ("dep::hash::collections", PathKind::Dep), - ("crate::std::hash", PathKind::Crate), - ]; - - for (src, expected_path_kind) in cases { - let path = parse_with(path(), src).unwrap(); - assert_eq!(path.kind, expected_path_kind); - } - - parse_all_failing( - path(), - vec!["dep", "crate", "crate::std::crate", "foo::bar::crate", "foo::dep"], - ); - } - #[test] fn parse_unary() { parse_all( diff --git a/compiler/noirc_frontend/src/parser/parser/path.rs b/compiler/noirc_frontend/src/parser/parser/path.rs new file mode 100644 index 00000000000..b062c8953b8 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/path.rs @@ -0,0 +1,78 @@ +use crate::parser::NoirParser; +use crate::{Path, PathKind}; + +use crate::token::{Keyword, Token}; + +use chumsky::prelude::*; + +use super::{ident, keyword}; + +pub(super) fn path() -> impl NoirParser { + let idents = || ident().separated_by(just(Token::DoubleColon)).at_least(1); + let make_path = |kind| move |segments, span| Path { segments, kind, span }; + + let prefix = |key| keyword(key).ignore_then(just(Token::DoubleColon)); + let path_kind = |key, kind| prefix(key).ignore_then(idents()).map_with_span(make_path(kind)); + + choice(( + path_kind(Keyword::Crate, PathKind::Crate), + path_kind(Keyword::Dep, PathKind::Dep), + idents().map_with_span(make_path(PathKind::Plain)), + )) +} + +fn empty_path() -> impl NoirParser { + let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; + let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); + + choice((path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Dep))) +} + +pub(super) fn maybe_empty_path() -> impl NoirParser { + path().or(empty_path()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::parser_test_helpers::{parse_all_failing, parse_with}; + + #[test] + fn parse_path() { + let cases = vec![ + ("std", vec!["std"]), + ("std::hash", vec!["std", "hash"]), + ("std::hash::collections", vec!["std", "hash", "collections"]), + ("dep::foo::bar", vec!["foo", "bar"]), + ("crate::std::hash", vec!["std", "hash"]), + ]; + + for (src, expected_segments) in cases { + let path: Path = parse_with(path(), src).unwrap(); + for (segment, expected) in path.segments.into_iter().zip(expected_segments) { + assert_eq!(segment.0.contents, expected); + } + } + + parse_all_failing(path(), vec!["std::", "::std", "std::hash::", "foo::1"]); + } + + #[test] + fn parse_path_kinds() { + let cases = vec![ + ("std", PathKind::Plain), + ("dep::hash::collections", PathKind::Dep), + ("crate::std::hash", PathKind::Crate), + ]; + + for (src, expected_path_kind) in cases { + let path = parse_with(path(), src).unwrap(); + assert_eq!(path.kind, expected_path_kind); + } + + parse_all_failing( + path(), + vec!["dep", "crate", "crate::std::crate", "foo::bar::crate", "foo::dep"], + ); + } +} From 1146f32046f80fb2c2ff12b8cf22537fb27406d3 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 16:35:01 +0000 Subject: [PATCH 03/12] chore: move function definitions into submodule --- compiler/noirc_frontend/src/parser/parser.rs | 225 ++---------------- .../src/parser/parser/function.rs | 223 +++++++++++++++++ 2 files changed, 243 insertions(+), 205 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/function.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index ed3948bb703..790206a998d 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -38,11 +38,10 @@ use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Attribute, Attributes, Keyword, SecondaryAttribute, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, - FunctionDefinition, FunctionReturnType, FunctionVisibility, Ident, IfExpression, - InfixExpression, LValue, Lambda, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, - NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TraitImplItem, - TraitItem, TypeImpl, UnaryOp, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, - UseTreeKind, Visibility, + FunctionReturnType, FunctionVisibility, Ident, IfExpression, InfixExpression, LValue, Lambda, + Literal, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Param, Path, Pattern, + Recoverable, Statement, TraitBound, TraitImplItem, TraitItem, TypeImpl, UnaryOp, + UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; use chumsky::prelude::*; @@ -50,6 +49,7 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; mod assertion; +mod function; mod path; use path::{maybe_empty_path, path}; @@ -114,7 +114,7 @@ fn top_level_statement( module_parser: impl NoirParser, ) -> impl NoirParser { choice(( - function_definition(false).map(TopLevelStatement::Function), + function::function_definition(false).map(TopLevelStatement::Function), struct_definition(), trait_definition(), trait_implementation(), @@ -165,94 +165,6 @@ fn contract(module_parser: impl NoirParser) -> impl NoirParser impl NoirParser { - attributes() - .then(function_modifiers()) - .then_ignore(keyword(Keyword::Fn)) - .then(ident()) - .then(generics()) - .then(parenthesized(function_parameters(allow_self))) - .then(function_return_type()) - .then(where_clause()) - .then(spanned(block(fresh_statement()))) - .validate(|(((args, ret), where_clause), (body, body_span)), span, emit| { - let ((((attributes, modifiers), name), generics), parameters) = args; - - // Validate collected attributes, filtering them into function and secondary variants - let attributes = validate_attributes(attributes, span, emit); - FunctionDefinition { - span: body_span, - name, - attributes, - is_unconstrained: modifiers.0, - is_open: modifiers.2, - is_internal: modifiers.3, - visibility: if modifiers.1 { - FunctionVisibility::PublicCrate - } else if modifiers.4 { - FunctionVisibility::Public - } else { - FunctionVisibility::Private - }, - generics, - parameters, - body, - where_clause, - return_type: ret.1, - return_visibility: ret.0 .1, - return_distinctness: ret.0 .0, - } - .into() - }) -} - -/// function_modifiers: 'unconstrained'? 'pub(crate)'? 'pub'? 'open'? 'internal'? -/// -/// returns (is_unconstrained, is_pub_crate, is_open, is_internal, is_pub) for whether each keyword was present -fn function_modifiers() -> impl NoirParser<(bool, bool, bool, bool, bool)> { - keyword(Keyword::Unconstrained) - .or_not() - .then(is_pub_crate()) - .then(keyword(Keyword::Pub).or_not()) - .then(keyword(Keyword::Open).or_not()) - .then(keyword(Keyword::Internal).or_not()) - .map(|((((unconstrained, pub_crate), public), open), internal)| { - ( - unconstrained.is_some(), - pub_crate, - open.is_some(), - internal.is_some(), - public.is_some(), - ) - }) -} - -fn is_pub_crate() -> impl NoirParser { - (keyword(Keyword::Pub) - .then_ignore(just(Token::LeftParen)) - .then_ignore(keyword(Keyword::Crate)) - .then_ignore(just(Token::RightParen))) - .or_not() - .map(|a| a.is_some()) -} - -/// non_empty_ident_list: ident ',' non_empty_ident_list -/// | ident -/// -/// generics: '<' non_empty_ident_list '>' -/// | %empty -fn generics() -> impl NoirParser> { - ident() - .separated_by(just(Token::Comma)) - .allow_trailing() - .at_least(1) - .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not() - .map(|opt| opt.unwrap_or_default()) -} - fn struct_definition() -> impl NoirParser { use self::Keyword::Struct; use Token::*; @@ -267,19 +179,22 @@ fn struct_definition() -> impl NoirParser { )) .or(just(Semicolon).to(Vec::new())); - attributes().then_ignore(keyword(Struct)).then(ident()).then(generics()).then(fields).validate( - |(((raw_attributes, name), generics), fields), span, emit| { + attributes() + .then_ignore(keyword(Struct)) + .then(ident()) + .then(function::generics()) + .then(fields) + .validate(|(((raw_attributes, name), generics), fields), span, emit| { let attributes = validate_struct_attributes(raw_attributes, span, emit); TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) - }, - ) + }) } fn type_alias_definition() -> impl NoirParser { use self::Keyword::Type; let p = ignore_then_commit(keyword(Type), ident()); - let p = then_commit(p, generics()); + let p = then_commit(p, function::generics()); let p = then_commit_ignore(p, just(Token::Assign)); let p = then_commit(p, parse_type()); @@ -343,31 +258,6 @@ fn lambda_parameters() -> impl NoirParser> { .labelled(ParsingRuleLabel::Parameter) } -fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a { - let typ = parse_type().recover_via(parameter_recovery()); - - let full_parameter = pattern() - .recover_via(parameter_name_recovery()) - .then_ignore(just(Token::Colon)) - .then(optional_visibility()) - .then(typ) - .map_with_span(|((pattern, visibility), typ), span| Param { - visibility, - pattern, - typ, - span, - }); - - let self_parameter = if allow_self { self_parameter().boxed() } else { nothing().boxed() }; - - let parameter = full_parameter.or(self_parameter); - - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} - /// This parser always parses no input and fails fn nothing() -> impl NoirParser { one_of([]).map(|_| unreachable!("parser should always error")) @@ -409,7 +299,7 @@ fn self_parameter() -> impl NoirParser { fn trait_definition() -> impl NoirParser { keyword(Keyword::Trait) .ignore_then(ident()) - .then(generics()) + .then(function::generics()) .then(where_clause()) .then_ignore(just(Token::LeftBrace)) .then(trait_body()) @@ -453,7 +343,7 @@ fn trait_function_declaration() -> impl NoirParser { keyword(Keyword::Fn) .ignore_then(ident()) - .then(generics()) + .then(function::generics()) .then(parenthesized(function_declaration_parameters())) .then(function_return_type().map(|(_, typ)| typ)) .then(where_clause()) @@ -559,10 +449,10 @@ fn trait_type_declaration() -> impl NoirParser { /// implementation: 'impl' generics type '{' function_definition ... '}' fn implementation() -> impl NoirParser { keyword(Keyword::Impl) - .ignore_then(generics()) + .ignore_then(function::generics()) .then(parse_type().map_with_span(|typ, span| (typ, span))) .then_ignore(just(Token::LeftBrace)) - .then(spanned(function_definition(true)).repeated()) + .then(spanned(function::function_definition(true)).repeated()) .then_ignore(just(Token::RightBrace)) .map(|((generics, (object_type, type_span)), methods)| { TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) @@ -576,7 +466,7 @@ fn implementation() -> impl NoirParser { /// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' fn trait_implementation() -> impl NoirParser { keyword(Keyword::Impl) - .ignore_then(generics()) + .ignore_then(function::generics()) .then(path()) .then(generic_type_args(parse_type())) .then_ignore(keyword(Keyword::For)) @@ -601,7 +491,7 @@ fn trait_implementation() -> impl NoirParser { } fn trait_implementation_body() -> impl NoirParser> { - let function = function_definition(true).validate(|mut f, span, emit| { + let function = function::function_definition(true).validate(|mut f, span, emit| { if f.def().is_internal || f.def().is_unconstrained || f.def().is_open @@ -1734,30 +1624,6 @@ mod test { use super::*; use crate::{ArrayLiteral, Literal}; - #[test] - fn regression_skip_comment() { - parse_all( - function_definition(false), - vec![ - "fn main( - // This comment should be skipped - x : Field, - // And this one - y : Field, - ) { - }", - "fn main(x : Field, y : Field,) { - foo::bar( - // Comment for x argument - x, - // Comment for y argument - y - ) - }", - ], - ); - } - #[test] fn parse_infix() { let valid = vec!["x + 6", "x - k", "x + (x + a)", " x * (x + a) + (x - 4)"]; @@ -1941,57 +1807,6 @@ mod test { ); } - #[test] - fn parse_function() { - parse_all( - function_definition(false), - vec![ - "fn func_name() {}", - "fn f(foo: pub u8, y : pub Field) -> u8 { x + a }", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) {}", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", - "fn func_name(x: [Field], y : [Field;2],y : pub [Field;2], z : pub [u8;5]) {}", - "fn main(x: pub u8, y: pub u8) -> distinct pub [u8; 2] { [x, y] }", - "fn f(f: pub Field, y : Field, z : comptime Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : comptime Field) -> u8 { x + a }", - "fn func_name(f: Field, y : T) where T: SomeTrait {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait, T: SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 + TraitY {}", - "fn func_name(f: Field, y : T, z : U) where SomeStruct: SomeTrait {}", - // 'where u32: SomeTrait' is allowed in Rust. - // It will result in compiler error in case SomeTrait isn't implemented for u32. - "fn func_name(f: Field, y : T) where u32: SomeTrait {}", - // A trailing plus is allowed by Rust, so we support it as well. - "fn func_name(f: Field, y : T) where T: SomeTrait + {}", - // The following should produce compile error on later stage. From the parser's perspective it's fine - "fn func_name(f: Field, y : Field, z : Field) where T: SomeTrait {}", - ], - ); - - parse_all_failing( - function_definition(false), - vec![ - "fn x2( f: []Field,,) {}", - "fn ( f: []Field) {}", - "fn ( f: []Field) {}", - // TODO: Check for more specific error messages - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: {}", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where SomeTrait {}", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) SomeTrait {}", - // A leading plus is not allowed. - "fn func_name(f: Field, y : T) where T: + SomeTrait {}", - "fn func_name(f: Field, y : T) where T: TraitX + {}", - ], - ); - } - #[test] fn parse_trait() { parse_all( diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs new file mode 100644 index 00000000000..a7909f7b961 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -0,0 +1,223 @@ +use super::{ + attributes, block, fresh_statement, ident, keyword, nothing, optional_distinctness, + optional_visibility, parameter_name_recovery, parameter_recovery, parenthesized, parse_type, + pattern, self_parameter, validate_attributes, where_clause, NoirParser, +}; +use crate::parser::labels::ParsingRuleLabel; +use crate::parser::spanned; +use crate::token::{Keyword, Token}; +use crate::{ + Distinctness, FunctionDefinition, FunctionReturnType, FunctionVisibility, Ident, NoirFunction, + Param, Visibility, +}; + +use chumsky::prelude::*; + +/// function_definition: attribute function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block +/// function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block +pub(super) fn function_definition(allow_self: bool) -> impl NoirParser { + attributes() + .then(function_modifiers()) + .then_ignore(keyword(Keyword::Fn)) + .then(ident()) + .then(generics()) + .then(parenthesized(function_parameters(allow_self))) + .then(function_return_type()) + .then(where_clause()) + .then(spanned(block(fresh_statement()))) + .validate(|(((args, ret), where_clause), (body, body_span)), span, emit| { + let ((((attributes, modifiers), name), generics), parameters) = args; + + // Validate collected attributes, filtering them into function and secondary variants + let attributes = validate_attributes(attributes, span, emit); + FunctionDefinition { + span: body_span, + name, + attributes, + is_unconstrained: modifiers.0, + is_open: modifiers.2, + is_internal: modifiers.3, + visibility: if modifiers.1 { + FunctionVisibility::PublicCrate + } else if modifiers.4 { + FunctionVisibility::Public + } else { + FunctionVisibility::Private + }, + generics, + parameters, + body, + where_clause, + return_type: ret.1, + return_visibility: ret.0 .1, + return_distinctness: ret.0 .0, + } + .into() + }) +} + +/// function_modifiers: 'unconstrained'? 'pub(crate)'? 'pub'? 'open'? 'internal'? +/// +/// returns (is_unconstrained, is_pub_crate, is_open, is_internal, is_pub) for whether each keyword was present +fn function_modifiers() -> impl NoirParser<(bool, bool, bool, bool, bool)> { + keyword(Keyword::Unconstrained) + .or_not() + .then(is_pub_crate()) + .then(keyword(Keyword::Pub).or_not()) + .then(keyword(Keyword::Open).or_not()) + .then(keyword(Keyword::Internal).or_not()) + .map(|((((unconstrained, pub_crate), public), open), internal)| { + ( + unconstrained.is_some(), + pub_crate, + open.is_some(), + internal.is_some(), + public.is_some(), + ) + }) +} + +fn is_pub_crate() -> impl NoirParser { + (keyword(Keyword::Pub) + .then_ignore(just(Token::LeftParen)) + .then_ignore(keyword(Keyword::Crate)) + .then_ignore(just(Token::RightParen))) + .or_not() + .map(|a| a.is_some()) +} + +/// non_empty_ident_list: ident ',' non_empty_ident_list +/// | ident +/// +/// generics: '<' non_empty_ident_list '>' +/// | %empty +pub(super) fn generics() -> impl NoirParser> { + ident() + .separated_by(just(Token::Comma)) + .allow_trailing() + .at_least(1) + .delimited_by(just(Token::Less), just(Token::Greater)) + .or_not() + .map(|opt| opt.unwrap_or_default()) +} + +fn function_return_type() -> impl NoirParser<((Distinctness, Visibility), FunctionReturnType)> { + just(Token::Arrow) + .ignore_then(optional_distinctness()) + .then(optional_visibility()) + .then(spanned(parse_type())) + .or_not() + .map_with_span(|ret, span| match ret { + Some((head, (ty, _))) => (head, FunctionReturnType::Ty(ty)), + None => ( + (Distinctness::DuplicationAllowed, Visibility::Private), + FunctionReturnType::Default(span), + ), + }) +} + +fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a { + let typ = parse_type().recover_via(parameter_recovery()); + + let full_parameter = pattern() + .recover_via(parameter_name_recovery()) + .then_ignore(just(Token::Colon)) + .then(optional_visibility()) + .then(typ) + .map_with_span(|((pattern, visibility), typ), span| Param { + visibility, + pattern, + typ, + span, + }); + + let self_parameter = if allow_self { self_parameter().boxed() } else { nothing().boxed() }; + + let parameter = full_parameter.or(self_parameter); + + parameter + .separated_by(just(Token::Comma)) + .allow_trailing() + .labelled(ParsingRuleLabel::Parameter) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::parser_test_helpers::*; + + #[test] + fn regression_skip_comment() { + parse_all( + function_definition(false), + vec![ + "fn main( + // This comment should be skipped + x : Field, + // And this one + y : Field, + ) { + }", + "fn main(x : Field, y : Field,) { + foo::bar( + // Comment for x argument + x, + // Comment for y argument + y + ) + }", + ], + ); + } + + #[test] + fn parse_function() { + parse_all( + function_definition(false), + vec![ + "fn func_name() {}", + "fn f(foo: pub u8, y : pub Field) -> u8 { x + a }", + "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) {}", + "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", + "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", + "fn func_name(x: [Field], y : [Field;2],y : pub [Field;2], z : pub [u8;5]) {}", + "fn main(x: pub u8, y: pub u8) -> distinct pub [u8; 2] { [x, y] }", + "fn f(f: pub Field, y : Field, z : comptime Field) -> u8 { x + a }", + "fn f(f: pub Field, y : T, z : comptime Field) -> u8 { x + a }", + "fn func_name(f: Field, y : T) where T: SomeTrait {}", + "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", + "fn func_name(f: Field, y : T) where T: SomeTrait, T: SomeTrait2 {}", + "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", + "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", + "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", + "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", + "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 + TraitY {}", + "fn func_name(f: Field, y : T, z : U) where SomeStruct: SomeTrait {}", + // 'where u32: SomeTrait' is allowed in Rust. + // It will result in compiler error in case SomeTrait isn't implemented for u32. + "fn func_name(f: Field, y : T) where u32: SomeTrait {}", + // A trailing plus is allowed by Rust, so we support it as well. + "fn func_name(f: Field, y : T) where T: SomeTrait + {}", + // The following should produce compile error on later stage. From the parser's perspective it's fine + "fn func_name(f: Field, y : Field, z : Field) where T: SomeTrait {}", + ], + ); + + parse_all_failing( + function_definition(false), + vec![ + "fn x2( f: []Field,,) {}", + "fn ( f: []Field) {}", + "fn ( f: []Field) {}", + // TODO: Check for more specific error messages + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: {}", + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where SomeTrait {}", + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) SomeTrait {}", + // A leading plus is not allowed. + "fn func_name(f: Field, y : T) where T: + SomeTrait {}", + "fn func_name(f: Field, y : T) where T: TraitX + {}", + ], + ); + } +} From 974ade68e05192f042496ce88de8ddb13a28cace Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 16:50:14 +0000 Subject: [PATCH 04/12] chore: move primitives into separate module --- compiler/noirc_frontend/src/parser/parser.rs | 218 +---------------- .../src/parser/parser/primitives.rs | 230 ++++++++++++++++++ 2 files changed, 235 insertions(+), 213 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/primitives.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 790206a998d..d63556c84f7 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -23,6 +23,8 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. +use self::primitives::{keyword, literal, mutable_reference, variable}; + use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, parenthesized, then_commit, then_commit_ignore, top_level_statement_recovery, ExprParser, @@ -40,7 +42,7 @@ use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, FunctionReturnType, FunctionVisibility, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Param, Path, Pattern, - Recoverable, Statement, TraitBound, TraitImplItem, TraitItem, TypeImpl, UnaryOp, + Recoverable, Statement, TraitBound, TraitImplItem, TraitItem, TypeImpl, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; @@ -51,8 +53,10 @@ use noirc_errors::{Span, Spanned}; mod assertion; mod function; mod path; +mod primitives; use path::{maybe_empty_path, path}; +use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; /// Entry function for the parser - also handles lexing internally. /// @@ -258,11 +262,6 @@ fn lambda_parameters() -> impl NoirParser> { .labelled(ParsingRuleLabel::Parameter) } -/// This parser always parses no input and fails -fn nothing() -> impl NoirParser { - one_of([]).map(|_| unreachable!("parser should always error")) -} - fn self_parameter() -> impl NoirParser { let mut_ref_pattern = just(Token::Ampersand).then_ignore(keyword(Keyword::Mut)); let mut_pattern = keyword(Keyword::Mut); @@ -608,24 +607,6 @@ fn use_statement() -> impl NoirParser { keyword(Keyword::Use).ignore_then(use_tree()).map(TopLevelStatement::Import) } -fn keyword(keyword: Keyword) -> impl NoirParser { - just(Token::Keyword(keyword)) -} - -fn token_kind(token_kind: TokenKind) -> impl NoirParser { - filter_map(move |span, found: Token| { - if found.kind() == token_kind { - Ok(found) - } else { - Err(ParserError::expected_label( - ParsingRuleLabel::TokenKind(token_kind.clone()), - found, - span, - )) - } - }) -} - fn rename() -> impl NoirParser> { ignore_then_commit(keyword(Keyword::As), ident()).or_not() } @@ -652,10 +633,6 @@ fn use_tree() -> impl NoirParser { }) } -fn ident() -> impl NoirParser { - token_kind(TokenKind::Ident).map_with_span(Ident::from_token) -} - fn statement<'a, P, P2>( expr_parser: P, expr_no_constructors: P2, @@ -1124,13 +1101,6 @@ fn create_infix_expression(lhs: Expression, (operator, rhs): (BinaryOp, Expressi Expression { span, kind: ExpressionKind::Infix(infix) } } -// Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier -// to parse nested generic types. For normal expressions however, it means we have to manually -// parse two greater-than tokens as a single right-shift here. -fn right_shift_operator() -> impl NoirParser { - just(Token::Greater).then(just(Token::Greater)).to(Token::ShiftRight) -} - fn operator_with_precedence(precedence: Precedence) -> impl NoirParser> { right_shift_operator() .or(any()) // Parse any single token, we're validating it as an operator next @@ -1346,41 +1316,6 @@ where expr_parser.separated_by(just(Token::Comma)).allow_trailing() } -fn not

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Bang).ignore_then(term_parser).map(|rhs| ExpressionKind::prefix(UnaryOp::Not, rhs)) -} - -fn negation

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Minus) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::Minus, rhs)) -} - -fn mutable_reference

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Ampersand) - .ignore_then(keyword(Keyword::Mut)) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::MutableReference, rhs)) -} - -fn dereference

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Star) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::Dereference { implicitly_added: false }, rhs)) -} - /// Atoms are parameterized on whether constructor expressions are allowed or not. /// Certain constructs like `if` and `for` disallow constructor expressions when a /// block may be expected. @@ -1471,21 +1406,6 @@ where long_form.or(short_form) } -fn variable() -> impl NoirParser { - path().map(ExpressionKind::Variable) -} - -fn literal() -> impl NoirParser { - token_kind(TokenKind::Literal).map(|token| match token { - Token::Int(x) => ExpressionKind::integer(x), - Token::Bool(b) => ExpressionKind::boolean(b), - Token::Str(s) => ExpressionKind::string(s), - Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), - Token::FmtStr(s) => ExpressionKind::format_string(s), - unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), - }) -} - #[cfg(test)] mod parser_test_helpers { use chumsky::primitive::just; @@ -1864,65 +1784,12 @@ mod test { ); } - fn expr_to_lit(expr: ExpressionKind) -> Literal { - match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - } - } - - #[test] - fn parse_int() { - let int = parse_with(literal(), "5").unwrap(); - let hex = parse_with(literal(), "0x05").unwrap(); - - match (expr_to_lit(int), expr_to_lit(hex)) { - (Literal::Integer(int, false), Literal::Integer(hex, false)) => assert_eq!(int, hex), - _ => unreachable!(), - } - } - - #[test] - fn parse_string() { - let expr = parse_with(literal(), r#""hello""#).unwrap(); - match expr_to_lit(expr) { - Literal::Str(s) => assert_eq!(s, "hello"), - _ => unreachable!(), - }; - } - - #[test] - fn parse_bool() { - let expr_true = parse_with(literal(), "true").unwrap(); - let expr_false = parse_with(literal(), "false").unwrap(); - - match (expr_to_lit(expr_true), expr_to_lit(expr_false)) { - (Literal::Bool(t), Literal::Bool(f)) => { - assert!(t); - assert!(!f); - } - _ => unreachable!(), - }; - } - #[test] fn parse_module_declaration() { parse_with(module_declaration(), "mod foo").unwrap(); parse_with(module_declaration(), "mod 1").unwrap_err(); } - #[test] - fn parse_unary() { - parse_all( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], - ); - parse_all_failing( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["+hello", "/hello"], - ); - } - #[test] fn parse_use() { parse_all( @@ -2145,79 +2012,4 @@ mod test { check_cases_with_errors(&cases[..], block(fresh_statement())); } - - #[test] - fn parse_raw_string_expr() { - let cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // mismatch: short: - Case { source: r##" r"foo"# "##, expect: r#"r"foo""#, errors: 1 }, - Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, - // empty string - Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, - Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, - // miscellaneous - Case { source: r##" r#\"foo\"# "##, expect: "plain::r", errors: 2 }, - Case { source: r#" r\"foo\" "#, expect: "plain::r", errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // missing 'r' letter - Case { source: r##" ##"foo"# "##, expect: r#""foo""#, errors: 2 }, - Case { source: r#" #"foo" "#, expect: "plain::foo", errors: 2 }, - // whitespace - Case { source: r##" r #"foo"# "##, expect: "plain::r", errors: 2 }, - Case { source: r##" r# "foo"# "##, expect: "plain::r", errors: 3 }, - Case { source: r#" r#"foo" # "#, expect: "(none)", errors: 2 }, - // after identifier - Case { source: r##" bar#"foo"# "##, expect: "plain::bar", errors: 2 }, - // nested - Case { - source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - errors: 0, - }, - ]; - - check_cases_with_errors(&cases[..], expression()); - } - - #[test] - fn parse_raw_string_lit() { - let lit_cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - ]; - - check_cases_with_errors(&lit_cases[..], literal()); - } } diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs new file mode 100644 index 00000000000..1b5ef581058 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -0,0 +1,230 @@ +use chumsky::prelude::*; + +use crate::{ + parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, + token::{Keyword, Token, TokenKind}, + ExpressionKind, Ident, UnaryOp, +}; + +use super::path; + +/// This parser always parses no input and fails +pub(super) fn nothing() -> impl NoirParser { + one_of([]).map(|_| unreachable!("parser should always error")) +} + +pub(super) fn keyword(keyword: Keyword) -> impl NoirParser { + just(Token::Keyword(keyword)) +} + +pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser { + filter_map(move |span, found: Token| { + if found.kind() == token_kind { + Ok(found) + } else { + Err(ParserError::expected_label( + ParsingRuleLabel::TokenKind(token_kind.clone()), + found, + span, + )) + } + }) +} + +pub(super) fn ident() -> impl NoirParser { + token_kind(TokenKind::Ident).map_with_span(Ident::from_token) +} + +// Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier +// to parse nested generic types. For normal expressions however, it means we have to manually +// parse two greater-than tokens as a single right-shift here. +pub(super) fn right_shift_operator() -> impl NoirParser { + just(Token::Greater).then(just(Token::Greater)).to(Token::ShiftRight) +} + +pub(super) fn not

(term_parser: P) -> impl NoirParser +where + P: ExprParser, +{ + just(Token::Bang).ignore_then(term_parser).map(|rhs| ExpressionKind::prefix(UnaryOp::Not, rhs)) +} + +pub(super) fn negation

(term_parser: P) -> impl NoirParser +where + P: ExprParser, +{ + just(Token::Minus) + .ignore_then(term_parser) + .map(|rhs| ExpressionKind::prefix(UnaryOp::Minus, rhs)) +} + +pub(super) fn mutable_reference

(term_parser: P) -> impl NoirParser +where + P: ExprParser, +{ + just(Token::Ampersand) + .ignore_then(keyword(Keyword::Mut)) + .ignore_then(term_parser) + .map(|rhs| ExpressionKind::prefix(UnaryOp::MutableReference, rhs)) +} + +pub(super) fn dereference

(term_parser: P) -> impl NoirParser +where + P: ExprParser, +{ + just(Token::Star) + .ignore_then(term_parser) + .map(|rhs| ExpressionKind::prefix(UnaryOp::Dereference { implicitly_added: false }, rhs)) +} + +pub(super) fn variable() -> impl NoirParser { + path().map(ExpressionKind::Variable) +} + +pub(super) fn literal() -> impl NoirParser { + token_kind(TokenKind::Literal).map(|token| match token { + Token::Int(x) => ExpressionKind::integer(x), + Token::Bool(b) => ExpressionKind::boolean(b), + Token::Str(s) => ExpressionKind::string(s), + Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), + Token::FmtStr(s) => ExpressionKind::format_string(s), + unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::{ + expression, expression_no_constructors, fresh_statement, parser_test_helpers::*, term, + }; + use crate::Literal; + + fn expr_to_lit(expr: ExpressionKind) -> Literal { + match expr { + ExpressionKind::Literal(literal) => literal, + _ => unreachable!("expected a literal"), + } + } + + #[test] + fn parse_int() { + let int = parse_with(literal(), "5").unwrap(); + let hex = parse_with(literal(), "0x05").unwrap(); + + match (expr_to_lit(int), expr_to_lit(hex)) { + (Literal::Integer(int, false), Literal::Integer(hex, false)) => assert_eq!(int, hex), + _ => unreachable!(), + } + } + + #[test] + fn parse_string() { + let expr = parse_with(literal(), r#""hello""#).unwrap(); + match expr_to_lit(expr) { + Literal::Str(s) => assert_eq!(s, "hello"), + _ => unreachable!(), + }; + } + + #[test] + fn parse_bool() { + let expr_true = parse_with(literal(), "true").unwrap(); + let expr_false = parse_with(literal(), "false").unwrap(); + + match (expr_to_lit(expr_true), expr_to_lit(expr_false)) { + (Literal::Bool(t), Literal::Bool(f)) => { + assert!(t); + assert!(!f); + } + _ => unreachable!(), + }; + } + + #[test] + fn parse_unary() { + parse_all( + term(expression(), expression_no_constructors(expression()), fresh_statement(), true), + vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], + ); + parse_all_failing( + term(expression(), expression_no_constructors(expression()), fresh_statement(), true), + vec!["+hello", "/hello"], + ); + } + + #[test] + fn parse_raw_string_expr() { + let cases = vec![ + Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, + Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, + // backslash + Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, + Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, + Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // escape sequence + Case { + source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, + expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, + errors: 0, + }, + Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, + // mismatch - errors: + Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, + Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, + // mismatch: short: + Case { source: r##" r"foo"# "##, expect: r#"r"foo""#, errors: 1 }, + Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, + // empty string + Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, + Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, + // miscellaneous + Case { source: r##" r#\"foo\"# "##, expect: "plain::r", errors: 2 }, + Case { source: r#" r\"foo\" "#, expect: "plain::r", errors: 1 }, + Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, + // missing 'r' letter + Case { source: r##" ##"foo"# "##, expect: r#""foo""#, errors: 2 }, + Case { source: r#" #"foo" "#, expect: "plain::foo", errors: 2 }, + // whitespace + Case { source: r##" r #"foo"# "##, expect: "plain::r", errors: 2 }, + Case { source: r##" r# "foo"# "##, expect: "plain::r", errors: 3 }, + Case { source: r#" r#"foo" # "#, expect: "(none)", errors: 2 }, + // after identifier + Case { source: r##" bar#"foo"# "##, expect: "plain::bar", errors: 2 }, + // nested + Case { + source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, + expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, + errors: 0, + }, + ]; + + check_cases_with_errors(&cases[..], expression()); + } + + #[test] + fn parse_raw_string_lit() { + let lit_cases = vec![ + Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, + Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, + // backslash + Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, + Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, + Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // escape sequence + Case { + source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, + expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, + errors: 0, + }, + Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, + // mismatch - errors: + Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, + Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, + ]; + + check_cases_with_errors(&lit_cases[..], literal()); + } +} From b3ffbf1f38f9aa395c82ef4400002c88004d1975 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:02:12 +0000 Subject: [PATCH 05/12] chore: move traits parsing into submodule --- compiler/noirc_frontend/src/parser/parser.rs | 162 +------------ .../src/parser/parser/traits.rs | 217 ++++++++++++++++++ 2 files changed, 222 insertions(+), 157 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/traits.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index d63556c84f7..da8aa92685c 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -40,9 +40,8 @@ use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Attribute, Attributes, Keyword, SecondaryAttribute, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, - FunctionReturnType, FunctionVisibility, Ident, IfExpression, InfixExpression, LValue, Lambda, - Literal, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Param, Path, Pattern, - Recoverable, Statement, TraitBound, TraitImplItem, TraitItem, TypeImpl, + FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, NoirStruct, + NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; @@ -54,6 +53,7 @@ mod assertion; mod function; mod path; mod primitives; +mod traits; use path::{maybe_empty_path, path}; use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; @@ -120,8 +120,8 @@ fn top_level_statement( choice(( function::function_definition(false).map(TopLevelStatement::Function), struct_definition(), - trait_definition(), - trait_implementation(), + traits::trait_definition(), + traits::trait_implementation(), implementation(), type_alias_definition().then_ignore(force(just(Token::Semicolon))), submodule(module_parser.clone()), @@ -295,63 +295,6 @@ fn self_parameter() -> impl NoirParser { }) } -fn trait_definition() -> impl NoirParser { - keyword(Keyword::Trait) - .ignore_then(ident()) - .then(function::generics()) - .then(where_clause()) - .then_ignore(just(Token::LeftBrace)) - .then(trait_body()) - .then_ignore(just(Token::RightBrace)) - .map_with_span(|(((name, generics), where_clause), items), span| { - TopLevelStatement::Trait(NoirTrait { name, generics, where_clause, span, items }) - }) -} - -fn trait_body() -> impl NoirParser> { - trait_function_declaration() - .or(trait_type_declaration()) - .or(trait_constant_declaration()) - .repeated() -} - -fn optional_default_value() -> impl NoirParser> { - ignore_then_commit(just(Token::Assign), expression()).or_not() -} - -fn trait_constant_declaration() -> impl NoirParser { - keyword(Keyword::Let) - .ignore_then(ident()) - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .then(optional_default_value()) - .then_ignore(just(Token::Semicolon)) - .validate(|((name, typ), default_value), span, emit| { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Associated constants"), - span, - )); - TraitItem::Constant { name, typ, default_value } - }) -} - -/// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type -fn trait_function_declaration() -> impl NoirParser { - let trait_function_body_or_semicolon = - block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).to(Option::None)); - - keyword(Keyword::Fn) - .ignore_then(ident()) - .then(function::generics()) - .then(parenthesized(function_declaration_parameters())) - .then(function_return_type().map(|(_, typ)| typ)) - .then(where_clause()) - .then(trait_function_body_or_semicolon) - .map(|(((((name, generics), parameters), return_type), where_clause), body)| { - TraitItem::Function { name, generics, parameters, return_type, where_clause, body } - }) -} - fn validate_attributes( attributes: Vec, span: Span, @@ -430,19 +373,6 @@ fn function_declaration_parameters() -> impl NoirParser impl NoirParser { - keyword(Keyword::Type).ignore_then(ident()).then_ignore(just(Token::Semicolon)).validate( - |name, span, emit| { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Associated types"), - span, - )); - TraitItem::Type { name } - }, - ) -} - /// Parses a non-trait implementation, adding a set of methods to a type. /// /// implementation: 'impl' generics type '{' function_definition ... '}' @@ -458,61 +388,6 @@ fn implementation() -> impl NoirParser { }) } -/// Parses a trait implementation, implementing a particular trait for a type. -/// This has a similar syntax to `implementation`, but the `for type` clause is required, -/// and an optional `where` clause is also useable. -/// -/// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' -fn trait_implementation() -> impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(path()) - .then(generic_type_args(parse_type())) - .then_ignore(keyword(Keyword::For)) - .then(parse_type()) - .then(where_clause()) - .then_ignore(just(Token::LeftBrace)) - .then(trait_implementation_body()) - .then_ignore(just(Token::RightBrace)) - .map(|args| { - let ((other_args, where_clause), items) = args; - let (((impl_generics, trait_name), trait_generics), object_type) = other_args; - - TopLevelStatement::TraitImpl(NoirTraitImpl { - impl_generics, - trait_name, - trait_generics, - object_type, - items, - where_clause, - }) - }) -} - -fn trait_implementation_body() -> impl NoirParser> { - let function = function::function_definition(true).validate(|mut f, span, emit| { - if f.def().is_internal - || f.def().is_unconstrained - || f.def().is_open - || f.def().visibility != FunctionVisibility::Private - { - emit(ParserError::with_reason(ParserErrorReason::TraitImplFunctionModifiers, span)); - } - // Trait impl functions are always public - f.def_mut().visibility = FunctionVisibility::Public; - TraitImplItem::Function(f) - }); - - let alias = keyword(Keyword::Type) - .ignore_then(ident()) - .then_ignore(just(Token::Assign)) - .then(parse_type()) - .then_ignore(just(Token::Semicolon)) - .map(|(name, alias)| TraitImplItem::Type { name, alias }); - - function.or(alias).repeated() -} - fn where_clause() -> impl NoirParser> { struct MultiTraitConstraint { typ: UnresolvedType, @@ -1727,33 +1602,6 @@ mod test { ); } - #[test] - fn parse_trait() { - parse_all( - trait_definition(), - vec![ - // Empty traits are legal in Rust and sometimes used as a way to whitelist certain types - // for a particular operation. Also known as `tag` or `marker` traits: - // https://stackoverflow.com/questions/71895489/what-is-the-purpose-of-defining-empty-impl-in-rust - "trait Empty {}", - "trait TraitWithDefaultBody { fn foo(self) {} }", - "trait TraitAcceptingMutableRef { fn foo(&mut self); }", - "trait TraitWithTypeBoundOperation { fn identity() -> Self; }", - "trait TraitWithAssociatedType { type Element; fn item(self, index: Field) -> Self::Element; }", - "trait TraitWithAssociatedConstant { let Size: Field; }", - "trait TraitWithAssociatedConstantWithDefaultValue { let Size: Field = 10; }", - "trait GenericTrait { fn elem(&mut self, index: Field) -> T; }", - "trait GenericTraitWithConstraints where T: SomeTrait { fn elem(self, index: Field) -> T; }", - "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait { let Size: Field; fn zero() -> Self; }", - ], - ); - - parse_all_failing( - trait_definition(), - vec!["trait MissingBody", "trait WrongDelimiter { fn foo() -> u8, fn bar() -> u8 }"], - ); - } - #[test] fn parse_parenthesized_expression() { parse_all( diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs new file mode 100644 index 00000000000..a613413b741 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -0,0 +1,217 @@ +use chumsky::prelude::*; + +use super::{ + block, expression, fresh_statement, function, function_declaration_parameters, + function_return_type, +}; + +use crate::{ + parser::{ + ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, + ParserErrorReason, TopLevelStatement, + }, + token::{Keyword, Token}, + Expression, FunctionVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, TraitItem, + UnresolvedTraitConstraint, UnresolvedType, +}; + +use super::{generic_type_args, parse_type, path, primitives::ident}; + +pub(super) fn trait_definition() -> impl NoirParser { + keyword(Keyword::Trait) + .ignore_then(ident()) + .then(function::generics()) + .then(where_clause()) + .then_ignore(just(Token::LeftBrace)) + .then(trait_body()) + .then_ignore(just(Token::RightBrace)) + .map_with_span(|(((name, generics), where_clause), items), span| { + TopLevelStatement::Trait(NoirTrait { name, generics, where_clause, span, items }) + }) +} + +fn trait_body() -> impl NoirParser> { + trait_function_declaration() + .or(trait_type_declaration()) + .or(trait_constant_declaration()) + .repeated() +} + +fn optional_default_value() -> impl NoirParser> { + ignore_then_commit(just(Token::Assign), expression()).or_not() +} + +fn trait_constant_declaration() -> impl NoirParser { + keyword(Keyword::Let) + .ignore_then(ident()) + .then_ignore(just(Token::Colon)) + .then(parse_type()) + .then(optional_default_value()) + .then_ignore(just(Token::Semicolon)) + .validate(|((name, typ), default_value), span, emit| { + emit(ParserError::with_reason( + ParserErrorReason::ExperimentalFeature("Associated constants"), + span, + )); + TraitItem::Constant { name, typ, default_value } + }) +} + +/// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type +fn trait_function_declaration() -> impl NoirParser { + let trait_function_body_or_semicolon = + block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).to(Option::None)); + + keyword(Keyword::Fn) + .ignore_then(ident()) + .then(function::generics()) + .then(parenthesized(function_declaration_parameters())) + .then(function_return_type().map(|(_, typ)| typ)) + .then(where_clause()) + .then(trait_function_body_or_semicolon) + .map(|(((((name, generics), parameters), return_type), where_clause), body)| { + TraitItem::Function { name, generics, parameters, return_type, where_clause, body } + }) +} + +/// trait_type_declaration: 'type' ident generics +fn trait_type_declaration() -> impl NoirParser { + keyword(Keyword::Type).ignore_then(ident()).then_ignore(just(Token::Semicolon)).validate( + |name, span, emit| { + emit(ParserError::with_reason( + ParserErrorReason::ExperimentalFeature("Associated types"), + span, + )); + TraitItem::Type { name } + }, + ) +} + +/// Parses a trait implementation, implementing a particular trait for a type. +/// This has a similar syntax to `implementation`, but the `for type` clause is required, +/// and an optional `where` clause is also useable. +/// +/// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' +pub(super) fn trait_implementation() -> impl NoirParser { + keyword(Keyword::Impl) + .ignore_then(function::generics()) + .then(path()) + .then(generic_type_args(parse_type())) + .then_ignore(keyword(Keyword::For)) + .then(parse_type()) + .then(where_clause()) + .then_ignore(just(Token::LeftBrace)) + .then(trait_implementation_body()) + .then_ignore(just(Token::RightBrace)) + .map(|args| { + let ((other_args, where_clause), items) = args; + let (((impl_generics, trait_name), trait_generics), object_type) = other_args; + + TopLevelStatement::TraitImpl(NoirTraitImpl { + impl_generics, + trait_name, + trait_generics, + object_type, + items, + where_clause, + }) + }) +} + +fn trait_implementation_body() -> impl NoirParser> { + let function = function::function_definition(true).validate(|mut f, span, emit| { + if f.def().is_internal + || f.def().is_unconstrained + || f.def().is_open + || f.def().visibility != FunctionVisibility::Private + { + emit(ParserError::with_reason(ParserErrorReason::TraitImplFunctionModifiers, span)); + } + // Trait impl functions are always public + f.def_mut().visibility = FunctionVisibility::Public; + TraitImplItem::Function(f) + }); + + let alias = keyword(Keyword::Type) + .ignore_then(ident()) + .then_ignore(just(Token::Assign)) + .then(parse_type()) + .then_ignore(just(Token::Semicolon)) + .map(|(name, alias)| TraitImplItem::Type { name, alias }); + + function.or(alias).repeated() +} + +fn where_clause() -> impl NoirParser> { + struct MultiTraitConstraint { + typ: UnresolvedType, + trait_bounds: Vec, + } + + let constraints = parse_type() + .then_ignore(just(Token::Colon)) + .then(trait_bounds()) + .map(|(typ, trait_bounds)| MultiTraitConstraint { typ, trait_bounds }); + + keyword(Keyword::Where) + .ignore_then(constraints.separated_by(just(Token::Comma))) + .or_not() + .map(|option| option.unwrap_or_default()) + .map(|x: Vec| { + let mut result: Vec = Vec::new(); + for constraint in x { + for bound in constraint.trait_bounds { + result.push(UnresolvedTraitConstraint { + typ: constraint.typ.clone(), + trait_bound: bound, + }); + } + } + result + }) +} + +fn trait_bounds() -> impl NoirParser> { + trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() +} + +fn trait_bound() -> impl NoirParser { + path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { + trait_path, + trait_generics, + trait_id: None, + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::parser_test_helpers::*; + + #[test] + fn parse_trait() { + parse_all( + trait_definition(), + vec![ + // Empty traits are legal in Rust and sometimes used as a way to whitelist certain types + // for a particular operation. Also known as `tag` or `marker` traits: + // https://stackoverflow.com/questions/71895489/what-is-the-purpose-of-defining-empty-impl-in-rust + "trait Empty {}", + "trait TraitWithDefaultBody { fn foo(self) {} }", + "trait TraitAcceptingMutableRef { fn foo(&mut self); }", + "trait TraitWithTypeBoundOperation { fn identity() -> Self; }", + "trait TraitWithAssociatedType { type Element; fn item(self, index: Field) -> Self::Element; }", + "trait TraitWithAssociatedConstant { let Size: Field; }", + "trait TraitWithAssociatedConstantWithDefaultValue { let Size: Field = 10; }", + "trait GenericTrait { fn elem(&mut self, index: Field) -> T; }", + "trait GenericTraitWithConstraints where T: SomeTrait { fn elem(self, index: Field) -> T; }", + "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait { let Size: Field; fn zero() -> Self; }", + ], + ); + + parse_all_failing( + trait_definition(), + vec!["trait MissingBody", "trait WrongDelimiter { fn foo() -> u8, fn bar() -> u8 }"], + ); + } +} From d7260ae0af1a9e60331049c449d6dc9095d839a9 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:13:31 +0000 Subject: [PATCH 06/12] chore: move struct parsing into submodule --- compiler/noirc_frontend/src/parser/parser.rs | 81 +------------ .../src/parser/parser/structs.rs | 113 ++++++++++++++++++ 2 files changed, 119 insertions(+), 75 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/structs.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index da8aa92685c..65f17a44383 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -37,11 +37,11 @@ use crate::ast::{ }; use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Attribute, Attributes, Keyword, SecondaryAttribute, Token, TokenKind}; +use crate::token::{Attribute, Attributes, Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, - FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, NoirStruct, - NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, + FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, + NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; @@ -53,6 +53,7 @@ mod assertion; mod function; mod path; mod primitives; +mod structs; mod traits; use path::{maybe_empty_path, path}; @@ -119,10 +120,10 @@ fn top_level_statement( ) -> impl NoirParser { choice(( function::function_definition(false).map(TopLevelStatement::Function), - struct_definition(), + structs::struct_definition(), + structs::implementation(), traits::trait_definition(), traits::trait_implementation(), - implementation(), type_alias_definition().then_ignore(force(just(Token::Semicolon))), submodule(module_parser.clone()), contract(module_parser), @@ -169,31 +170,6 @@ fn contract(module_parser: impl NoirParser) -> impl NoirParser impl NoirParser { - use self::Keyword::Struct; - use Token::*; - - let fields = struct_fields() - .delimited_by(just(LeftBrace), just(RightBrace)) - .recover_with(nested_delimiters( - LeftBrace, - RightBrace, - [(LeftParen, RightParen), (LeftBracket, RightBracket)], - |_| vec![], - )) - .or(just(Semicolon).to(Vec::new())); - - attributes() - .then_ignore(keyword(Struct)) - .then(ident()) - .then(function::generics()) - .then(fields) - .validate(|(((raw_attributes, name), generics), fields), span, emit| { - let attributes = validate_struct_attributes(raw_attributes, span, emit); - TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) - }) -} - fn type_alias_definition() -> impl NoirParser { use self::Keyword::Type; @@ -240,14 +216,6 @@ fn attributes() -> impl NoirParser> { attribute().repeated() } -fn struct_fields() -> impl NoirParser> { - ident() - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .separated_by(just(Token::Comma)) - .allow_trailing() -} - fn lambda_parameters() -> impl NoirParser> { let typ = parse_type().recover_via(parameter_recovery()); let typ = just(Token::Colon).ignore_then(typ); @@ -321,28 +289,6 @@ fn validate_attributes( Attributes { function: primary, secondary } } -fn validate_struct_attributes( - attributes: Vec, - span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Vec { - let mut struct_attributes = vec![]; - - for attribute in attributes { - match attribute { - Attribute::Function(..) => { - emit(ParserError::with_reason( - ParserErrorReason::NoFunctionAttributesAllowedOnStruct, - span, - )); - } - Attribute::Secondary(attr) => struct_attributes.push(attr), - } - } - - struct_attributes -} - /// Function declaration parameters differ from other parameters in that parameter /// patterns are not allowed in declarations. All parameters must be identifiers. fn function_declaration_parameters() -> impl NoirParser> { @@ -373,21 +319,6 @@ fn function_declaration_parameters() -> impl NoirParser impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(parse_type().map_with_span(|typ, span| (typ, span))) - .then_ignore(just(Token::LeftBrace)) - .then(spanned(function::function_definition(true)).repeated()) - .then_ignore(just(Token::RightBrace)) - .map(|((generics, (object_type, type_span)), methods)| { - TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) - }) -} - fn where_clause() -> impl NoirParser> { struct MultiTraitConstraint { typ: UnresolvedType, diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs new file mode 100644 index 00000000000..7e6c9cb1511 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -0,0 +1,113 @@ +use chumsky::prelude::*; +use noirc_errors::Span; + +use crate::{ + macros_api::SecondaryAttribute, + parser::{ + parser::{ + attributes, function, + primitives::{ident, keyword}, + }, + spanned, NoirParser, ParserError, ParserErrorReason, TopLevelStatement, + }, + token::{Attribute, Keyword, Token}, + Ident, NoirStruct, TypeImpl, UnresolvedType, +}; + +use super::parse_type; + +pub(super) fn struct_definition() -> impl NoirParser { + use self::Keyword::Struct; + use Token::*; + + let fields = struct_fields() + .delimited_by(just(LeftBrace), just(RightBrace)) + .recover_with(nested_delimiters( + LeftBrace, + RightBrace, + [(LeftParen, RightParen), (LeftBracket, RightBracket)], + |_| vec![], + )) + .or(just(Semicolon).to(Vec::new())); + + attributes() + .then_ignore(keyword(Struct)) + .then(ident()) + .then(function::generics()) + .then(fields) + .validate(|(((raw_attributes, name), generics), fields), span, emit| { + let attributes = validate_struct_attributes(raw_attributes, span, emit); + TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) + }) +} + +fn struct_fields() -> impl NoirParser> { + ident() + .then_ignore(just(Token::Colon)) + .then(parse_type()) + .separated_by(just(Token::Comma)) + .allow_trailing() +} + +fn validate_struct_attributes( + attributes: Vec, + span: Span, + emit: &mut dyn FnMut(ParserError), +) -> Vec { + let mut struct_attributes = vec![]; + + for attribute in attributes { + match attribute { + Attribute::Function(..) => { + emit(ParserError::with_reason( + ParserErrorReason::NoFunctionAttributesAllowedOnStruct, + span, + )); + } + Attribute::Secondary(attr) => struct_attributes.push(attr), + } + } + + struct_attributes +} + +/// Parses a non-trait implementation, adding a set of methods to a type. +/// +/// implementation: 'impl' generics type '{' function_definition ... '}' +pub(super) fn implementation() -> impl NoirParser { + keyword(Keyword::Impl) + .ignore_then(function::generics()) + .then(parse_type().map_with_span(|typ, span| (typ, span))) + .then_ignore(just(Token::LeftBrace)) + .then(spanned(function::function_definition(true)).repeated()) + .then_ignore(just(Token::RightBrace)) + .map(|((generics, (object_type, type_span)), methods)| { + TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::parser_test_helpers::*; + + #[test] + fn parse_structs() { + let cases = vec![ + "struct Foo;", + "struct Foo { }", + "struct Bar { ident: Field, }", + "struct Baz { ident: Field, other: Field }", + "#[attribute] struct Baz { ident: Field, other: Field }", + ]; + parse_all(struct_definition(), cases); + + let failing = vec![ + "struct { }", + "struct Foo { bar: pub Field }", + "struct Foo { bar: pub Field }", + "#[oracle(some)] struct Foo { bar: Field }", + ]; + parse_all_failing(struct_definition(), failing); + } +} From 04543381150292f7363c69b4d785fbaba545dc23 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:17:03 +0000 Subject: [PATCH 07/12] chore: move parser test helpers into a separate file --- compiler/noirc_frontend/src/parser/parser.rs | 137 +------------- .../src/parser/parser/assertion.rs | 2 +- .../src/parser/parser/function.rs | 2 +- .../noirc_frontend/src/parser/parser/path.rs | 2 +- .../src/parser/parser/primitives.rs | 2 +- .../src/parser/parser/structs.rs | 2 +- .../src/parser/parser/test_helpers.rs | 122 +++++++++++++ .../src/parser/parser/traits.rs | 2 +- .../noirc_frontend/src/parser/parser/types.rs | 172 ++++++++++++++++++ 9 files changed, 304 insertions(+), 139 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/test_helpers.rs create mode 100644 compiler/noirc_frontend/src/parser/parser/types.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 65f17a44383..fac37ab606f 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -56,6 +56,9 @@ mod primitives; mod structs; mod traits; +#[cfg(test)] +mod test_helpers; + use path::{maybe_empty_path, path}; use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; @@ -1212,141 +1215,9 @@ where long_form.or(short_form) } -#[cfg(test)] -mod parser_test_helpers { - use chumsky::primitive::just; - use chumsky::Parser; - use iter_extended::vecmap; - use noirc_errors::CustomDiagnostic; - - use crate::{ - lexer::Lexer, - parser::{force, NoirParser}, - token::Token, - }; - - pub(crate) fn parse_with(parser: P, program: &str) -> Result> - where - P: NoirParser, - { - let (tokens, lexer_errors) = Lexer::lex(program); - if !lexer_errors.is_empty() { - return Err(vecmap(lexer_errors, Into::into)); - } - parser - .then_ignore(just(Token::EOF)) - .parse(tokens) - .map_err(|errors| vecmap(errors, Into::into)) - } - - pub(crate) fn parse_recover( - parser: P, - program: &str, - ) -> (Option, Vec) - where - P: NoirParser, - { - let (tokens, lexer_errors) = Lexer::lex(program); - let (opt, errs) = parser.then_ignore(force(just(Token::EOF))).parse_recovery(tokens); - - let mut errors = vecmap(lexer_errors, Into::into); - errors.extend(errs.into_iter().map(Into::into)); - - (opt, errors) - } - - pub(crate) fn parse_all(parser: P, programs: Vec<&str>) -> Vec - where - P: NoirParser, - { - vecmap(programs, move |program| { - let message = format!("Failed to parse:\n{program}"); - let (op_t, diagnostics) = parse_recover(&parser, program); - diagnostics.iter().for_each(|diagnostic| { - if diagnostic.is_error() { - panic!("{} with error {}", &message, diagnostic); - } - }); - op_t.expect(&message) - }) - } - - pub(crate) fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec - where - P: NoirParser, - T: std::fmt::Display, - { - programs - .into_iter() - .flat_map(|program| match parse_with(&parser, program) { - Ok(expr) => { - unreachable!( - "Expected this input to fail:\n{}\nYet it successfully parsed as:\n{}", - program, expr - ) - } - Err(diagnostics) => { - if diagnostics.iter().all(|diagnostic: &CustomDiagnostic| diagnostic.is_warning()) { - unreachable!( - "Expected at least one error when parsing:\n{}\nYet it successfully parsed without errors:\n", - program - ) - }; - diagnostics - } - }) - .collect() - } - - #[derive(Copy, Clone)] - pub(crate) struct Case { - pub(crate) source: &'static str, - pub(crate) errors: usize, - pub(crate) expect: &'static str, - } - - pub(crate) fn check_cases_with_errors(cases: &[Case], parser: P) - where - P: NoirParser + Clone, - T: std::fmt::Display, - { - let show_errors = |v| vecmap(&v, ToString::to_string).join("\n"); - - let results = vecmap(cases, |&case| { - let (opt, errors) = parse_recover(parser.clone(), case.source); - let actual = opt.map(|ast| ast.to_string()); - let actual = if let Some(s) = &actual { s.to_string() } else { "(none)".to_string() }; - - let result = ((errors.len(), actual.clone()), (case.errors, case.expect.to_string())); - if result.0 != result.1 { - let num_errors = errors.len(); - let shown_errors = show_errors(errors); - eprintln!( - concat!( - "\nExpected {expected_errors} error(s) and got {num_errors}:", - "\n\n{shown_errors}", - "\n\nFrom input: {src}", - "\nExpected AST: {expected_result}", - "\nActual AST: {actual}\n", - ), - expected_errors = case.errors, - num_errors = num_errors, - shown_errors = shown_errors, - src = case.source, - expected_result = case.expect, - actual = actual, - ); - } - result - }); - - assert_eq!(vecmap(&results, |t| t.0.clone()), vecmap(&results, |t| t.1.clone()),); - } -} - #[cfg(test)] mod test { - use super::parser_test_helpers::*; + use super::test_helpers::*; use super::*; use crate::{ArrayLiteral, Literal}; diff --git a/compiler/noirc_frontend/src/parser/parser/assertion.rs b/compiler/noirc_frontend/src/parser/parser/assertion.rs index dae4464eff6..f9c8d7aa46b 100644 --- a/compiler/noirc_frontend/src/parser/parser/assertion.rs +++ b/compiler/noirc_frontend/src/parser/parser/assertion.rs @@ -76,7 +76,7 @@ mod test { use crate::{ parser::parser::{ expression, - parser_test_helpers::{parse_all, parse_all_failing, parse_with}, + test_helpers::{parse_all, parse_all_failing, parse_with}, }, Literal, }; diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index a7909f7b961..1b940b61a9e 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -144,7 +144,7 @@ fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a #[cfg(test)] mod test { use super::*; - use crate::parser::parser::parser_test_helpers::*; + use crate::parser::parser::test_helpers::*; #[test] fn regression_skip_comment() { diff --git a/compiler/noirc_frontend/src/parser/parser/path.rs b/compiler/noirc_frontend/src/parser/parser/path.rs index b062c8953b8..ab812c07dce 100644 --- a/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/compiler/noirc_frontend/src/parser/parser/path.rs @@ -35,7 +35,7 @@ pub(super) fn maybe_empty_path() -> impl NoirParser { #[cfg(test)] mod test { use super::*; - use crate::parser::parser::parser_test_helpers::{parse_all_failing, parse_with}; + use crate::parser::parser::test_helpers::{parse_all_failing, parse_with}; #[test] fn parse_path() { diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs index 1b5ef581058..ddb7abb5b29 100644 --- a/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -96,7 +96,7 @@ pub(super) fn literal() -> impl NoirParser { mod test { use super::*; use crate::parser::parser::{ - expression, expression_no_constructors, fresh_statement, parser_test_helpers::*, term, + expression, expression_no_constructors, fresh_statement, term, test_helpers::*, }; use crate::Literal; diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs index 7e6c9cb1511..f2bb63e60dc 100644 --- a/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -89,7 +89,7 @@ pub(super) fn implementation() -> impl NoirParser { #[cfg(test)] mod test { use super::*; - use crate::parser::parser::parser_test_helpers::*; + use crate::parser::parser::test_helpers::*; #[test] fn parse_structs() { diff --git a/compiler/noirc_frontend/src/parser/parser/test_helpers.rs b/compiler/noirc_frontend/src/parser/parser/test_helpers.rs new file mode 100644 index 00000000000..6b8cb80a0a0 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/test_helpers.rs @@ -0,0 +1,122 @@ +use chumsky::primitive::just; +use chumsky::Parser; +use iter_extended::vecmap; +use noirc_errors::CustomDiagnostic; + +use crate::{ + lexer::Lexer, + parser::{force, NoirParser}, + token::Token, +}; + +pub(crate) fn parse_with(parser: P, program: &str) -> Result> +where + P: NoirParser, +{ + let (tokens, lexer_errors) = Lexer::lex(program); + if !lexer_errors.is_empty() { + return Err(vecmap(lexer_errors, Into::into)); + } + parser.then_ignore(just(Token::EOF)).parse(tokens).map_err(|errors| vecmap(errors, Into::into)) +} + +pub(crate) fn parse_recover(parser: P, program: &str) -> (Option, Vec) +where + P: NoirParser, +{ + let (tokens, lexer_errors) = Lexer::lex(program); + let (opt, errs) = parser.then_ignore(force(just(Token::EOF))).parse_recovery(tokens); + + let mut errors = vecmap(lexer_errors, Into::into); + errors.extend(errs.into_iter().map(Into::into)); + + (opt, errors) +} + +pub(crate) fn parse_all(parser: P, programs: Vec<&str>) -> Vec +where + P: NoirParser, +{ + vecmap(programs, move |program| { + let message = format!("Failed to parse:\n{program}"); + let (op_t, diagnostics) = parse_recover(&parser, program); + diagnostics.iter().for_each(|diagnostic| { + if diagnostic.is_error() { + panic!("{} with error {}", &message, diagnostic); + } + }); + op_t.expect(&message) + }) +} + +pub(crate) fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec +where + P: NoirParser, + T: std::fmt::Display, +{ + programs + .into_iter() + .flat_map(|program| match parse_with(&parser, program) { + Ok(expr) => { + unreachable!( + "Expected this input to fail:\n{}\nYet it successfully parsed as:\n{}", + program, expr + ) + } + Err(diagnostics) => { + if diagnostics.iter().all(|diagnostic: &CustomDiagnostic| diagnostic.is_warning()) { + unreachable!( + "Expected at least one error when parsing:\n{}\nYet it successfully parsed without errors:\n", + program + ) + }; + diagnostics + } + }) + .collect() +} + +#[derive(Copy, Clone)] +pub(crate) struct Case { + pub(crate) source: &'static str, + pub(crate) errors: usize, + pub(crate) expect: &'static str, +} + +pub(crate) fn check_cases_with_errors(cases: &[Case], parser: P) +where + P: NoirParser + Clone, + T: std::fmt::Display, +{ + let show_errors = |v| vecmap(&v, ToString::to_string).join("\n"); + + let results = vecmap(cases, |&case| { + let (opt, errors) = parse_recover(parser.clone(), case.source); + let actual = opt.map(|ast| ast.to_string()); + let actual = if let Some(s) = &actual { s.to_string() } else { "(none)".to_string() }; + + let result = ((errors.len(), actual.clone()), (case.errors, case.expect.to_string())); + if result.0 != result.1 { + let num_errors = errors.len(); + let shown_errors = show_errors(errors); + eprintln!( + concat!( + "\nExpected {expected_errors} error(s) and got {num_errors}:", + "\n\n{shown_errors}", + "\n\nFrom input: {src}", + "\nExpected AST: {expected_result}", + "\nActual AST: {actual}\n", + ), + expected_errors = case.errors, + num_errors = num_errors, + shown_errors = shown_errors, + src = case.source, + expected_result = case.expect, + actual = actual, + ); + } + result + }); + + assert_eq!(vecmap(&results, |t| t.0.clone()), vecmap(&results, |t| t.1.clone()),); +} diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index a613413b741..0d72fbd5303 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -186,7 +186,7 @@ fn trait_bound() -> impl NoirParser { #[cfg(test)] mod test { use super::*; - use crate::parser::parser::parser_test_helpers::*; + use crate::parser::parser::test_helpers::*; #[test] fn parse_trait() { diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs new file mode 100644 index 00000000000..572397d6527 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -0,0 +1,172 @@ +use super::{ + expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, + ParserErrorReason, Precedence, +}; +use crate::ast::{UnresolvedType, UnresolvedTypeData}; + +use crate::parser::labels::ParsingRuleLabel; +use crate::token::{Keyword, Token}; +use crate::{Recoverable, UnresolvedTypeExpression}; + +use chumsky::prelude::*; +use noirc_errors::Span; + +fn maybe_comp_time() -> impl NoirParser<()> { + keyword(Keyword::CompTime).or_not().validate(|opt, span, emit| { + if opt.is_some() { + emit(ParserError::with_reason(ParserErrorReason::ComptimeDeprecated, span)); + } + }) +} + +pub(super) fn parenthesized_type( + recursive_type_parser: impl NoirParser, +) -> impl NoirParser { + recursive_type_parser + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(|typ, span| UnresolvedType { + typ: UnresolvedTypeData::Parenthesized(Box::new(typ)), + span: span.into(), + }) +} + +pub(super) fn field_type() -> impl NoirParser { + maybe_comp_time() + .then_ignore(keyword(Keyword::Field)) + .map_with_span(|_, span| UnresolvedTypeData::FieldElement.with_span(span)) +} + +pub(super) fn bool_type() -> impl NoirParser { + maybe_comp_time() + .then_ignore(keyword(Keyword::Bool)) + .map_with_span(|_, span| UnresolvedTypeData::Bool.with_span(span)) +} + +pub(super) fn string_type() -> impl NoirParser { + keyword(Keyword::String) + .ignore_then( + type_expression().delimited_by(just(Token::Less), just(Token::Greater)).or_not(), + ) + .map_with_span(|expr, span| UnresolvedTypeData::String(expr).with_span(span)) +} + +pub(super) fn format_string_type( + type_parser: impl NoirParser, +) -> impl NoirParser { + keyword(Keyword::FormatString) + .ignore_then( + type_expression() + .then_ignore(just(Token::Comma)) + .then(type_parser) + .delimited_by(just(Token::Less), just(Token::Greater)), + ) + .map_with_span(|(size, fields), span| { + UnresolvedTypeData::FormatString(size, Box::new(fields)).with_span(span) + }) +} + +pub(super) fn int_type() -> impl NoirParser { + maybe_comp_time() + .then(filter_map(|span, token: Token| match token { + Token::IntType(int_type) => Ok(int_type), + unexpected => { + Err(ParserError::expected_label(ParsingRuleLabel::IntegerType, unexpected, span)) + } + })) + .validate(|(_, token), span, emit| { + UnresolvedTypeData::from_int_token(token) + .map(|data| data.with_span(span)) + .unwrap_or_else(|err| { + emit(ParserError::with_reason(ParserErrorReason::InvalidBitSize(err.0), span)); + UnresolvedType::error(span) + }) + }) +} + +pub(super) fn array_type( + type_parser: impl NoirParser, +) -> impl NoirParser { + just(Token::LeftBracket) + .ignore_then(type_parser) + .then(just(Token::Semicolon).ignore_then(type_expression()).or_not()) + .then_ignore(just(Token::RightBracket)) + .map_with_span(|(element_type, size), span| { + UnresolvedTypeData::Array(size, Box::new(element_type)).with_span(span) + }) +} + +pub(super) fn type_expression() -> impl NoirParser { + recursive(|expr| { + expression_with_precedence( + Precedence::lowest_type_precedence(), + expr, + nothing(), + nothing(), + true, + false, + ) + }) + .labelled(ParsingRuleLabel::TypeExpression) + .try_map(UnresolvedTypeExpression::from_expr) +} + +pub(super) fn tuple_type(type_parser: T) -> impl NoirParser +where + T: NoirParser, +{ + let fields = type_parser.separated_by(just(Token::Comma)).allow_trailing(); + parenthesized(fields).map_with_span(|fields, span| { + if fields.is_empty() { + UnresolvedTypeData::Unit.with_span(span) + } else { + UnresolvedTypeData::Tuple(fields).with_span(span) + } + }) +} + +pub(super) fn function_type(type_parser: T) -> impl NoirParser +where + T: NoirParser, +{ + let args = parenthesized(type_parser.clone().separated_by(just(Token::Comma)).allow_trailing()); + + let env = just(Token::LeftBracket) + .ignore_then(type_parser.clone()) + .then_ignore(just(Token::RightBracket)) + .or_not() + .map_with_span(|t, span| { + t.unwrap_or_else(|| UnresolvedTypeData::Unit.with_span(Span::empty(span.end()))) + }); + + keyword(Keyword::Fn) + .ignore_then(env) + .then(args) + .then_ignore(just(Token::Arrow)) + .then(type_parser) + .map_with_span(|((env, args), ret), span| { + UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env)).with_span(span) + }) +} + +pub(super) fn mutable_reference_type(type_parser: T) -> impl NoirParser +where + T: NoirParser, +{ + just(Token::Ampersand) + .ignore_then(keyword(Keyword::Mut)) + .ignore_then(type_parser) + .map_with_span(|element, span| { + UnresolvedTypeData::MutableReference(Box::new(element)).with_span(span) + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::test_helpers::*; + + #[test] + fn parse_type_expression() { + parse_all(type_expression(), vec!["(123)", "123", "(1 + 1)", "(1 + (1))"]); + } +} From e2762d6aa40953a692d15266fb60a830208c5fad Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:17:56 +0000 Subject: [PATCH 08/12] chore: remove duplicated test case --- compiler/noirc_frontend/src/parser/parser.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index fac37ab606f..2890f52f2b9 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -1471,26 +1471,6 @@ mod test { ); } - #[test] - fn parse_structs() { - let cases = vec![ - "struct Foo;", - "struct Foo { }", - "struct Bar { ident: Field, }", - "struct Baz { ident: Field, other: Field }", - "#[attribute] struct Baz { ident: Field, other: Field }", - ]; - parse_all(struct_definition(), cases); - - let failing = vec![ - "struct { }", - "struct Foo { bar: pub Field }", - "struct Foo { bar: pub Field }", - "#[oracle(some)] struct Foo { bar: Field }", - ]; - parse_all_failing(struct_definition(), failing); - } - #[test] fn parse_type_aliases() { let cases = vec!["type foo = u8", "type bar = String", "type baz = Vec"]; From fde37827a5faa189933a3a0d4deaef49fa4f5ea5 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:25:29 +0000 Subject: [PATCH 09/12] chore: move implementation() out of structs.rs --- compiler/noirc_frontend/src/parser/parser.rs | 19 +++++++++++++++++-- .../src/parser/parser/structs.rs | 19 ++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 2890f52f2b9..99e9a96976a 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -41,7 +41,7 @@ use crate::token::{Attribute, Attributes, Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, - NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, + NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; @@ -124,9 +124,9 @@ fn top_level_statement( choice(( function::function_definition(false).map(TopLevelStatement::Function), structs::struct_definition(), - structs::implementation(), traits::trait_definition(), traits::trait_implementation(), + implementation(), type_alias_definition().then_ignore(force(just(Token::Semicolon))), submodule(module_parser.clone()), contract(module_parser), @@ -137,6 +137,21 @@ fn top_level_statement( .recover_via(top_level_statement_recovery()) } +/// Parses a non-trait implementation, adding a set of methods to a type. +/// +/// implementation: 'impl' generics type '{' function_definition ... '}' +fn implementation() -> impl NoirParser { + keyword(Keyword::Impl) + .ignore_then(function::generics()) + .then(parse_type().map_with_span(|typ, span| (typ, span))) + .then_ignore(just(Token::LeftBrace)) + .then(spanned(function::function_definition(true)).repeated()) + .then_ignore(just(Token::RightBrace)) + .map(|((generics, (object_type, type_span)), methods)| { + TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) + }) +} + /// global_declaration: 'global' ident global_type_annotation '=' literal fn global_declaration() -> impl NoirParser { let p = ignore_then_commit( diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs index f2bb63e60dc..5d273130208 100644 --- a/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -8,10 +8,10 @@ use crate::{ attributes, function, primitives::{ident, keyword}, }, - spanned, NoirParser, ParserError, ParserErrorReason, TopLevelStatement, + NoirParser, ParserError, ParserErrorReason, TopLevelStatement, }, token::{Attribute, Keyword, Token}, - Ident, NoirStruct, TypeImpl, UnresolvedType, + Ident, NoirStruct, UnresolvedType, }; use super::parse_type; @@ -71,21 +71,6 @@ fn validate_struct_attributes( struct_attributes } -/// Parses a non-trait implementation, adding a set of methods to a type. -/// -/// implementation: 'impl' generics type '{' function_definition ... '}' -pub(super) fn implementation() -> impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(parse_type().map_with_span(|typ, span| (typ, span))) - .then_ignore(just(Token::LeftBrace)) - .then(spanned(function::function_definition(true)).repeated()) - .then_ignore(just(Token::RightBrace)) - .map(|((generics, (object_type, type_span)), methods)| { - TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) - }) -} - #[cfg(test)] mod test { use super::*; From 2b5df09ad14ca69ba809cd84ff46a4e6bbdf5d3a Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:40:37 +0000 Subject: [PATCH 10/12] chore: move literals into a separate file --- compiler/noirc_frontend/src/parser/parser.rs | 4 +- .../src/parser/parser/literals.rs | 157 ++++++++++++++++++ .../src/parser/parser/primitives.rs | 129 -------------- 3 files changed, 160 insertions(+), 130 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/literals.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 99e9a96976a..821d5a41100 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -23,7 +23,7 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. -use self::primitives::{keyword, literal, mutable_reference, variable}; +use self::primitives::{keyword, mutable_reference, variable}; use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, @@ -51,6 +51,7 @@ use noirc_errors::{Span, Spanned}; mod assertion; mod function; +mod literals; mod path; mod primitives; mod structs; @@ -59,6 +60,7 @@ mod traits; #[cfg(test)] mod test_helpers; +use literals::literal; use path::{maybe_empty_path, path}; use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; diff --git a/compiler/noirc_frontend/src/parser/parser/literals.rs b/compiler/noirc_frontend/src/parser/parser/literals.rs new file mode 100644 index 00000000000..32f4f03de2e --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/literals.rs @@ -0,0 +1,157 @@ +use chumsky::Parser; + +use crate::{ + parser::NoirParser, + token::{Token, TokenKind}, + ExpressionKind, +}; + +use super::primitives::token_kind; + +pub(super) fn literal() -> impl NoirParser { + token_kind(TokenKind::Literal).map(|token| match token { + Token::Int(x) => ExpressionKind::integer(x), + Token::Bool(b) => ExpressionKind::boolean(b), + Token::Str(s) => ExpressionKind::string(s), + Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), + Token::FmtStr(s) => ExpressionKind::format_string(s), + unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::parser::parser::{ + expression, expression_no_constructors, fresh_statement, term, test_helpers::*, + }; + use crate::Literal; + + fn expr_to_lit(expr: ExpressionKind) -> Literal { + match expr { + ExpressionKind::Literal(literal) => literal, + _ => unreachable!("expected a literal"), + } + } + + #[test] + fn parse_int() { + let int = parse_with(literal(), "5").unwrap(); + let hex = parse_with(literal(), "0x05").unwrap(); + + match (expr_to_lit(int), expr_to_lit(hex)) { + (Literal::Integer(int, false), Literal::Integer(hex, false)) => assert_eq!(int, hex), + _ => unreachable!(), + } + } + + #[test] + fn parse_string() { + let expr = parse_with(literal(), r#""hello""#).unwrap(); + match expr_to_lit(expr) { + Literal::Str(s) => assert_eq!(s, "hello"), + _ => unreachable!(), + }; + } + + #[test] + fn parse_bool() { + let expr_true = parse_with(literal(), "true").unwrap(); + let expr_false = parse_with(literal(), "false").unwrap(); + + match (expr_to_lit(expr_true), expr_to_lit(expr_false)) { + (Literal::Bool(t), Literal::Bool(f)) => { + assert!(t); + assert!(!f); + } + _ => unreachable!(), + }; + } + + #[test] + fn parse_unary() { + parse_all( + term(expression(), expression_no_constructors(expression()), fresh_statement(), true), + vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], + ); + parse_all_failing( + term(expression(), expression_no_constructors(expression()), fresh_statement(), true), + vec!["+hello", "/hello"], + ); + } + + #[test] + fn parse_raw_string_expr() { + let cases = vec![ + Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, + Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, + // backslash + Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, + Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, + Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // escape sequence + Case { + source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, + expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, + errors: 0, + }, + Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, + // mismatch - errors: + Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, + Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, + // mismatch: short: + Case { source: r##" r"foo"# "##, expect: r#"r"foo""#, errors: 1 }, + Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, + // empty string + Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, + Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, + // miscellaneous + Case { source: r##" r#\"foo\"# "##, expect: "plain::r", errors: 2 }, + Case { source: r#" r\"foo\" "#, expect: "plain::r", errors: 1 }, + Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, + // missing 'r' letter + Case { source: r##" ##"foo"# "##, expect: r#""foo""#, errors: 2 }, + Case { source: r#" #"foo" "#, expect: "plain::foo", errors: 2 }, + // whitespace + Case { source: r##" r #"foo"# "##, expect: "plain::r", errors: 2 }, + Case { source: r##" r# "foo"# "##, expect: "plain::r", errors: 3 }, + Case { source: r#" r#"foo" # "#, expect: "(none)", errors: 2 }, + // after identifier + Case { source: r##" bar#"foo"# "##, expect: "plain::bar", errors: 2 }, + // nested + Case { + source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, + expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, + errors: 0, + }, + ]; + + check_cases_with_errors(&cases[..], expression()); + } + + #[test] + fn parse_raw_string_lit() { + let lit_cases = vec![ + Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, + Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, + // backslash + Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, + Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, + Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // escape sequence + Case { + source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, + expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, + errors: 0, + }, + Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, + // mismatch - errors: + Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, + Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, + ]; + + check_cases_with_errors(&lit_cases[..], literal()); + } +} diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs index ddb7abb5b29..34927278038 100644 --- a/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -81,65 +81,11 @@ pub(super) fn variable() -> impl NoirParser { path().map(ExpressionKind::Variable) } -pub(super) fn literal() -> impl NoirParser { - token_kind(TokenKind::Literal).map(|token| match token { - Token::Int(x) => ExpressionKind::integer(x), - Token::Bool(b) => ExpressionKind::boolean(b), - Token::Str(s) => ExpressionKind::string(s), - Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), - Token::FmtStr(s) => ExpressionKind::format_string(s), - unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), - }) -} - #[cfg(test)] mod test { - use super::*; use crate::parser::parser::{ expression, expression_no_constructors, fresh_statement, term, test_helpers::*, }; - use crate::Literal; - - fn expr_to_lit(expr: ExpressionKind) -> Literal { - match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - } - } - - #[test] - fn parse_int() { - let int = parse_with(literal(), "5").unwrap(); - let hex = parse_with(literal(), "0x05").unwrap(); - - match (expr_to_lit(int), expr_to_lit(hex)) { - (Literal::Integer(int, false), Literal::Integer(hex, false)) => assert_eq!(int, hex), - _ => unreachable!(), - } - } - - #[test] - fn parse_string() { - let expr = parse_with(literal(), r#""hello""#).unwrap(); - match expr_to_lit(expr) { - Literal::Str(s) => assert_eq!(s, "hello"), - _ => unreachable!(), - }; - } - - #[test] - fn parse_bool() { - let expr_true = parse_with(literal(), "true").unwrap(); - let expr_false = parse_with(literal(), "false").unwrap(); - - match (expr_to_lit(expr_true), expr_to_lit(expr_false)) { - (Literal::Bool(t), Literal::Bool(f)) => { - assert!(t); - assert!(!f); - } - _ => unreachable!(), - }; - } #[test] fn parse_unary() { @@ -152,79 +98,4 @@ mod test { vec!["+hello", "/hello"], ); } - - #[test] - fn parse_raw_string_expr() { - let cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // mismatch: short: - Case { source: r##" r"foo"# "##, expect: r#"r"foo""#, errors: 1 }, - Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, - // empty string - Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, - Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, - // miscellaneous - Case { source: r##" r#\"foo\"# "##, expect: "plain::r", errors: 2 }, - Case { source: r#" r\"foo\" "#, expect: "plain::r", errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // missing 'r' letter - Case { source: r##" ##"foo"# "##, expect: r#""foo""#, errors: 2 }, - Case { source: r#" #"foo" "#, expect: "plain::foo", errors: 2 }, - // whitespace - Case { source: r##" r #"foo"# "##, expect: "plain::r", errors: 2 }, - Case { source: r##" r# "foo"# "##, expect: "plain::r", errors: 3 }, - Case { source: r#" r#"foo" # "#, expect: "(none)", errors: 2 }, - // after identifier - Case { source: r##" bar#"foo"# "##, expect: "plain::bar", errors: 2 }, - // nested - Case { - source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - errors: 0, - }, - ]; - - check_cases_with_errors(&cases[..], expression()); - } - - #[test] - fn parse_raw_string_lit() { - let lit_cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - ]; - - check_cases_with_errors(&lit_cases[..], literal()); - } } From 7774b50bbf26edb65cbe7b2fecb75fe52c3329e5 Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:40:56 +0000 Subject: [PATCH 11/12] chore: move attributes to a new file --- compiler/noirc_frontend/src/parser/parser.rs | 40 +--------------- .../src/parser/parser/attributes.rs | 46 +++++++++++++++++++ .../src/parser/parser/function.rs | 7 +-- .../src/parser/parser/structs.rs | 5 +- 4 files changed, 54 insertions(+), 44 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/attributes.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 821d5a41100..fcd0f5940eb 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -37,7 +37,7 @@ use crate::ast::{ }; use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Attribute, Attributes, Keyword, Token, TokenKind}; +use crate::token::{Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, @@ -50,6 +50,7 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; mod assertion; +mod attributes; mod function; mod literals; mod path; @@ -225,17 +226,6 @@ fn function_return_type() -> impl NoirParser<((Distinctness, Visibility), Functi }) } -fn attribute() -> impl NoirParser { - token_kind(TokenKind::Attribute).map(|token| match token { - Token::Attribute(attribute) => attribute, - _ => unreachable!("Parser should have already errored due to token not being an attribute"), - }) -} - -fn attributes() -> impl NoirParser> { - attribute().repeated() -} - fn lambda_parameters() -> impl NoirParser> { let typ = parse_type().recover_via(parameter_recovery()); let typ = just(Token::Colon).ignore_then(typ); @@ -283,32 +273,6 @@ fn self_parameter() -> impl NoirParser { }) } -fn validate_attributes( - attributes: Vec, - span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Attributes { - let mut primary = None; - let mut secondary = Vec::new(); - - for attribute in attributes { - match attribute { - Attribute::Function(attr) => { - if primary.is_some() { - emit(ParserError::with_reason( - ParserErrorReason::MultipleFunctionAttributesFound, - span, - )); - } - primary = Some(attr); - } - Attribute::Secondary(attr) => secondary.push(attr), - } - } - - Attributes { function: primary, secondary } -} - /// Function declaration parameters differ from other parameters in that parameter /// patterns are not allowed in declarations. All parameters must be identifiers. fn function_declaration_parameters() -> impl NoirParser> { diff --git a/compiler/noirc_frontend/src/parser/parser/attributes.rs b/compiler/noirc_frontend/src/parser/parser/attributes.rs new file mode 100644 index 00000000000..4b256a95c8b --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -0,0 +1,46 @@ +use chumsky::Parser; +use noirc_errors::Span; + +use crate::{ + parser::{NoirParser, ParserError, ParserErrorReason}, + token::{Attribute, Attributes, Token, TokenKind}, +}; + +use super::primitives::token_kind; + +fn attribute() -> impl NoirParser { + token_kind(TokenKind::Attribute).map(|token| match token { + Token::Attribute(attribute) => attribute, + _ => unreachable!("Parser should have already errored due to token not being an attribute"), + }) +} + +pub(super) fn attributes() -> impl NoirParser> { + attribute().repeated() +} + +pub(super) fn validate_attributes( + attributes: Vec, + span: Span, + emit: &mut dyn FnMut(ParserError), +) -> Attributes { + let mut primary = None; + let mut secondary = Vec::new(); + + for attribute in attributes { + match attribute { + Attribute::Function(attr) => { + if primary.is_some() { + emit(ParserError::with_reason( + ParserErrorReason::MultipleFunctionAttributesFound, + span, + )); + } + primary = Some(attr); + } + Attribute::Secondary(attr) => secondary.push(attr), + } + } + + Attributes { function: primary, secondary } +} diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index 1b940b61a9e..0d34c719061 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -1,7 +1,8 @@ use super::{ - attributes, block, fresh_statement, ident, keyword, nothing, optional_distinctness, - optional_visibility, parameter_name_recovery, parameter_recovery, parenthesized, parse_type, - pattern, self_parameter, validate_attributes, where_clause, NoirParser, + attributes::{attributes, validate_attributes}, + block, fresh_statement, ident, keyword, nothing, optional_distinctness, optional_visibility, + parameter_name_recovery, parameter_recovery, parenthesized, parse_type, pattern, + self_parameter, where_clause, NoirParser, }; use crate::parser::labels::ParsingRuleLabel; use crate::parser::spanned; diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs index 5d273130208..0212f56783f 100644 --- a/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -5,7 +5,8 @@ use crate::{ macros_api::SecondaryAttribute, parser::{ parser::{ - attributes, function, + attributes::attributes, + function, parse_type, primitives::{ident, keyword}, }, NoirParser, ParserError, ParserErrorReason, TopLevelStatement, @@ -14,8 +15,6 @@ use crate::{ Ident, NoirStruct, UnresolvedType, }; -use super::parse_type; - pub(super) fn struct_definition() -> impl NoirParser { use self::Keyword::Struct; use Token::*; From def29f367108c3d8c28f888e4ae50877b54bc95f Mon Sep 17 00:00:00 2001 From: Tom French Date: Sat, 24 Feb 2024 17:48:01 +0000 Subject: [PATCH 12/12] chore: split lambda functions into a separate file --- compiler/noirc_frontend/src/parser/parser.rs | 42 +++---------------- .../src/parser/parser/lambdas.rs | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 compiler/noirc_frontend/src/parser/parser/lambdas.rs diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index fcd0f5940eb..75f4a6359bf 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -40,9 +40,9 @@ use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, - FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, - NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, - UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, + FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Literal, NoirTypeAlias, + Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, UnresolvedTraitConstraint, + UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; use chumsky::prelude::*; @@ -52,6 +52,7 @@ use noirc_errors::{Span, Spanned}; mod assertion; mod attributes; mod function; +mod lambdas; mod literals; mod path; mod primitives; @@ -204,13 +205,6 @@ fn type_alias_definition() -> impl NoirParser { }) } -fn lambda_return_type() -> impl NoirParser { - just(Token::Arrow) - .ignore_then(parse_type()) - .or_not() - .map(|ret| ret.unwrap_or_else(UnresolvedType::unspecified)) -} - fn function_return_type() -> impl NoirParser<((Distinctness, Visibility), FunctionReturnType)> { just(Token::Arrow) .ignore_then(optional_distinctness()) @@ -226,20 +220,6 @@ fn function_return_type() -> impl NoirParser<((Distinctness, Visibility), Functi }) } -fn lambda_parameters() -> impl NoirParser> { - let typ = parse_type().recover_via(parameter_recovery()); - let typ = just(Token::Colon).ignore_then(typ); - - let parameter = pattern() - .recover_via(parameter_name_recovery()) - .then(typ.or_not().map(|typ| typ.unwrap_or_else(UnresolvedType::unspecified))); - - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} - fn self_parameter() -> impl NoirParser { let mut_ref_pattern = just(Token::Ampersand).then_ignore(keyword(Keyword::Mut)); let mut_pattern = keyword(Keyword::Mut); @@ -1030,18 +1010,6 @@ where }) } -fn lambda<'a>( - expr_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - lambda_parameters() - .delimited_by(just(Token::Pipe), just(Token::Pipe)) - .then(lambda_return_type()) - .then(expr_parser) - .map(|((parameters, return_type), body)| { - ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) - }) -} - fn for_loop<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a where P: ExprParser + 'a, @@ -1128,7 +1096,7 @@ where } else { nothing().boxed() }, - lambda(expr_parser.clone()), + lambdas::lambda(expr_parser.clone()), block(statement).map(ExpressionKind::Block), variable(), literal(), diff --git a/compiler/noirc_frontend/src/parser/parser/lambdas.rs b/compiler/noirc_frontend/src/parser/parser/lambdas.rs new file mode 100644 index 00000000000..48ddd41ab44 --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/lambdas.rs @@ -0,0 +1,42 @@ +use chumsky::{primitive::just, Parser}; + +use crate::{ + parser::{labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, NoirParser}, + token::Token, + Expression, ExpressionKind, Lambda, Pattern, UnresolvedType, +}; + +use super::{parse_type, pattern}; + +pub(super) fn lambda<'a>( + expr_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + lambda_parameters() + .delimited_by(just(Token::Pipe), just(Token::Pipe)) + .then(lambda_return_type()) + .then(expr_parser) + .map(|((parameters, return_type), body)| { + ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) + }) +} + +fn lambda_parameters() -> impl NoirParser> { + let typ = parse_type().recover_via(parameter_recovery()); + let typ = just(Token::Colon).ignore_then(typ); + + let parameter = pattern() + .recover_via(parameter_name_recovery()) + .then(typ.or_not().map(|typ| typ.unwrap_or_else(UnresolvedType::unspecified))); + + parameter + .separated_by(just(Token::Comma)) + .allow_trailing() + .labelled(ParsingRuleLabel::Parameter) +} + +fn lambda_return_type() -> impl NoirParser { + just(Token::Arrow) + .ignore_then(parse_type()) + .or_not() + .map(|ret| ret.unwrap_or_else(UnresolvedType::unspecified)) +}