Skip to content

Commit

Permalink
chore: Add some docs to the frontend (#903)
Browse files Browse the repository at this point in the history
* Add some docs

* s -> z

* Update crates/noirc_frontend/src/lexer/mod.rs

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>

* Update crates/noirc_frontend/src/hir_def/mod.rs

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>

* Update crates/noirc_frontend/src/hir_def/mod.rs

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>

* Add link to chumsky docs

* Update crates/noirc_frontend/src/lexer/token.rs

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>

* Fix warning

* Ammend docs on Token::Invalid

---------

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
  • Loading branch information
jfecher and TomAFrench authored Feb 28, 2023
1 parent 4a0145f commit a92d12e
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 297 deletions.
8 changes: 6 additions & 2 deletions crates/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/// This module contains two Ident structures, due to the fact that an identifier may or may not return a value
/// statement::Ident does not return a value, while Expression::Ident does.
//! The submodules of this module define the various data types required to
//! represent Noir's Ast. Of particular importance are ExpressionKind and Statement
//! which can be found in expression.rs and statement.rs respectively.
//!
//! Noir's Ast is produced by the parser and taken as input to name resolution,
//! where it is converted into the Hir (defined in the hir_def module).
mod expression;
mod function;
mod statement;
Expand Down
210 changes: 106 additions & 104 deletions crates/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,112 @@ use noirc_errors::{Span, Spanned};
/// for an identifier that already failed to parse.
pub const ERROR_IDENT: &str = "$error";

/// Ast node for statements in noir. Statements are always within a block { }
/// of some kind and are terminated via a Semicolon, except if the statement
/// ends in a block, such as a Statement::Expression containing an if expression.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Statement {
Let(LetStatement),
Constrain(ConstrainStatement),
Expression(Expression),
Assign(AssignStatement),
// This is an expression with a trailing semi-colon
Semi(Expression),
// This statement is the result of a recovered parse error.
// To avoid issuing multiple errors in later steps, it should
// be skipped in any future analysis if possible.
Error,
}

impl Recoverable for Statement {
fn error(_: Span) -> Self {
Statement::Error
}
}

impl Statement {
pub fn new_let(
((pattern, r#type), expression): ((Pattern, UnresolvedType), Expression),
) -> Statement {
Statement::Let(LetStatement { pattern, r#type, expression })
}

pub fn add_semicolon(
self,
semi: Option<Token>,
span: Span,
last_statement_in_block: bool,
emit_error: &mut dyn FnMut(ParserError),
) -> Statement {
match self {
Statement::Let(_)
| Statement::Constrain(_)
| Statement::Assign(_)
| Statement::Semi(_)
| Statement::Error => {
// To match rust, statements always require a semicolon, even at the end of a block
if semi.is_none() {
let reason = "Expected a ; separating these two statements".to_string();
emit_error(ParserError::with_reason(reason, span));
}
self
}

Statement::Expression(expr) => {
match (&expr.kind, semi, last_statement_in_block) {
// Semicolons are optional for these expressions
(ExpressionKind::Block(_), semi, _)
| (ExpressionKind::For(_), semi, _)
| (ExpressionKind::If(_), semi, _) => {
if semi.is_some() {
Statement::Semi(expr)
} else {
Statement::Expression(expr)
}
}

// Don't wrap expressions that are not the last expression in
// a block in a Semi so that we can report errors in the type checker
// for unneeded expressions like { 1 + 2; 3 }
(_, Some(_), false) => Statement::Expression(expr),
(_, None, false) => {
let reason = "Expected a ; separating these two statements".to_string();
emit_error(ParserError::with_reason(reason, span));
Statement::Expression(expr)
}

(_, Some(_), true) => Statement::Semi(expr),
(_, None, true) => Statement::Expression(expr),
}
}
}
}

/// Create a Statement::Assign value, desugaring any combined operators like += if needed.
pub fn assign(
lvalue: LValue,
operator: Token,
mut expression: Expression,
span: Span,
) -> Statement {
// Desugar `a <op>= b` to `a = a <op> b`. This relies on the evaluation of `a` having no side effects,
// which is currently enforced by the restricted syntax of LValues.
if operator != Token::Assign {
let lvalue_expr = lvalue.as_expression(span);
let error_msg = "Token passed to Statement::assign is not a binary operator";

let infix = crate::InfixExpression {
lhs: lvalue_expr,
operator: operator.try_into_binary_op(span).expect(error_msg),
rhs: expression,
};
expression = Expression::new(ExpressionKind::Infix(Box::new(infix)), span);
}

Statement::Assign(AssignStatement { lvalue, expression })
}
}

#[derive(Eq, Debug, Clone)]
pub struct Ident(pub Spanned<String>);

Expand Down Expand Up @@ -119,110 +225,6 @@ pub trait Recoverable {
fn error(span: Span) -> Self;
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Statement {
Let(LetStatement),
Constrain(ConstrainStatement),
Expression(Expression),
Assign(AssignStatement),
// This is an expression with a trailing semi-colon
// terminology Taken from rustc
Semi(Expression),
// This statement is the result of a recovered parse error.
// To avoid issuing multiple errors in later steps, it should
// be skipped in any future analysis if possible.
Error,
}

impl Recoverable for Statement {
fn error(_: Span) -> Self {
Statement::Error
}
}

impl Statement {
pub fn new_let(
((pattern, r#type), expression): ((Pattern, UnresolvedType), Expression),
) -> Statement {
Statement::Let(LetStatement { pattern, r#type, expression })
}

pub fn add_semicolon(
self,
semi: Option<Token>,
span: Span,
last_statement_in_block: bool,
emit_error: &mut dyn FnMut(ParserError),
) -> Statement {
match self {
Statement::Let(_)
| Statement::Constrain(_)
| Statement::Assign(_)
| Statement::Semi(_)
| Statement::Error => {
// To match rust, statements always require a semicolon, even at the end of a block
if semi.is_none() {
let reason = "Expected a ; separating these two statements".to_string();
emit_error(ParserError::with_reason(reason, span));
}
self
}

Statement::Expression(expr) => {
match (&expr.kind, semi, last_statement_in_block) {
// Semicolons are optional for these expressions
(ExpressionKind::Block(_), semi, _)
| (ExpressionKind::For(_), semi, _)
| (ExpressionKind::If(_), semi, _) => {
if semi.is_some() {
Statement::Semi(expr)
} else {
Statement::Expression(expr)
}
}

// Don't wrap expressions that are not the last expression in
// a block in a Semi so that we can report errors in the type checker
// for unneeded expressions like { 1 + 2; 3 }
(_, Some(_), false) => Statement::Expression(expr),
(_, None, false) => {
let reason = "Expected a ; separating these two statements".to_string();
emit_error(ParserError::with_reason(reason, span));
Statement::Expression(expr)
}

(_, Some(_), true) => Statement::Semi(expr),
(_, None, true) => Statement::Expression(expr),
}
}
}
}

/// Create a Statement::Assign value, desugaring any combined operators like += if needed.
pub fn assign(
lvalue: LValue,
operator: Token,
mut expression: Expression,
span: Span,
) -> Statement {
// Desugar `a <op>= b` to `a = a <op> b`. This relies on the evaluation of `a` having no side effects,
// which is currently enforced by the restricted syntax of LValues.
if operator != Token::Assign {
let lvalue_expr = lvalue.as_expression(span);
let error_msg = "Token passed to Statement::assign is not a binary operator";

let infix = crate::InfixExpression {
lhs: lvalue_expr,
operator: operator.try_into_binary_op(span).expect(error_msg),
rhs: expression,
};
expression = Expression::new(ExpressionKind::Infix(Box::new(infix)), span);
}

Statement::Assign(AssignStatement { lvalue, expression })
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ImportStatement {
pub path: Path,
Expand Down
17 changes: 17 additions & 0 deletions crates/noirc_frontend/src/hir_def/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
//! Noir's Hir is the result of the name resolution step (defined in the
//! hir module) and is essentially identical to the Ast with some small transformations.
//! The HIR is the input to the name resolution pass, the type checking pass, and the
//! monomorphisation pass.
//!
//! Name Resolution takes the AST as input and produces the initial Hir which strongly
//! resembles the Ast except:
//! - Variables now have DefinitionIDs that can be used to reference the definition that defines them.
//! - Modules & imports are removed in favor of these direct links to definitions
//! - Resolves names in UnresolvedTypes to produce resolved Types.
//!
//! Type checking takes the Hir and:
//! - Tags each DefinitionId with its type
//! - Replaces MethodCall nodes with FunctionCalls
//!
//! Finally, monomorphization takes the Hir and converts it to a monomorphized Ir
//! (monomorphization::ast::Expression).
pub mod expr;
pub mod function;
pub mod stmt;
Expand Down
Loading

0 comments on commit a92d12e

Please sign in to comment.