Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 63 additions & 5 deletions acvm-repo/acir/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use token::{Keyword, SpannedToken, Token};
use crate::{
BlackBoxFunc,
circuit::{
Circuit, Opcode, PublicInputs,
Circuit, Opcode, Program, PublicInputs,
brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs},
opcodes::{
AcirFunctionId, BlackBoxFuncCall, BlockId, BlockType, ConstantOrWitnessEnum,
Expand All @@ -34,6 +34,11 @@ impl AcirParserErrorWithSource {
fn parse_error(error: ParserError, src: &str) -> Self {
Self { src: src.to_string(), error }
}

#[cfg(test)]
pub(super) fn get_error(self) -> ParserError {
self.error
}
}

impl std::fmt::Debug for AcirParserErrorWithSource {
Expand Down Expand Up @@ -65,6 +70,28 @@ impl std::fmt::Debug for AcirParserErrorWithSource {
}
}

impl FromStr for Program<FieldElement> {
type Err = AcirParserErrorWithSource;

fn from_str(src: &str) -> Result<Self, Self::Err> {
Self::from_str_impl(src)
}
}

impl Program<FieldElement> {
/// Creates a [Program] object from the given string.
#[allow(clippy::should_implement_trait)]
pub fn from_str(src: &str) -> Result<Self, AcirParserErrorWithSource> {
FromStr::from_str(src)
}

pub fn from_str_impl(src: &str) -> Result<Self, AcirParserErrorWithSource> {
let mut parser =
Parser::new(src).map_err(|err| AcirParserErrorWithSource::parse_error(err, src))?;
parser.parse_program().map_err(|err| AcirParserErrorWithSource::parse_error(err, src))
}
}

impl FromStr for Circuit<FieldElement> {
type Err = AcirParserErrorWithSource;

Expand All @@ -83,7 +110,7 @@ impl Circuit<FieldElement> {
pub fn from_str_impl(src: &str) -> Result<Self, AcirParserErrorWithSource> {
let mut parser =
Parser::new(src).map_err(|err| AcirParserErrorWithSource::parse_error(err, src))?;
parser.parse_acir().map_err(|err| AcirParserErrorWithSource::parse_error(err, src))
parser.parse_circuit().map_err(|err| AcirParserErrorWithSource::parse_error(err, src))
}
}

Expand All @@ -100,7 +127,35 @@ impl<'a> Parser<'a> {
Ok(parser)
}

pub(crate) fn parse_acir(&mut self) -> ParseResult<Circuit<FieldElement>> {
/// Parse multiple [Circuit] blocks and return a [Program].
fn parse_program(&mut self) -> ParseResult<Program<FieldElement>> {
let mut functions: Vec<Circuit<FieldElement>> = Vec::new();

// We expect top-level "func" keywords for each circuit
while let Some(Keyword::Function) = self.peek_keyword() {
self.bump()?;

let func_id = self.eat_u32_or_error()?;
let expected_id = functions.len() as u32;
if func_id != expected_id {
return Err(ParserError::UnexpectedFunctionId {
expected: expected_id,
found: func_id,
span: self.token.span(),
});
}

let circuit = self.parse_circuit()?;

functions.push(circuit);
}

// We don't support parsing unconstrained Brillig bytecode blocks
let unconstrained_functions = Vec::new();
Ok(Program { functions, unconstrained_functions })
}

pub(crate) fn parse_circuit(&mut self) -> ParseResult<Circuit<FieldElement>> {
let current_witness_index = self.parse_current_witness_index()?;
let private_parameters = self.parse_private_parameters()?;
let public_parameters = PublicInputs(self.parse_public_parameters()?);
Expand Down Expand Up @@ -592,7 +647,7 @@ impl<'a> Parser<'a> {
fn parse_brillig_call(&mut self) -> ParseResult<Opcode<FieldElement>> {
self.eat_keyword_or_error(Keyword::Brillig)?;
self.eat_keyword_or_error(Keyword::Call)?;
self.eat_expected_ident("func")?;
self.eat_keyword_or_error(Keyword::Function)?;
let func_id = self.eat_u32_or_error()?;
self.eat_or_error(Token::Colon)?;

Expand Down Expand Up @@ -686,7 +741,7 @@ impl<'a> Parser<'a> {

fn parse_call(&mut self) -> ParseResult<Opcode<FieldElement>> {
self.eat_keyword_or_error(Keyword::Call)?;
self.eat_expected_ident("func")?;
self.eat_keyword_or_error(Keyword::Function)?;
let id = self.eat_u32_or_error()?;
self.eat_or_error(Token::Colon)?;
let predicate = self.eat_predicate()?;
Expand Down Expand Up @@ -986,6 +1041,8 @@ pub(crate) enum ParserError {
IncorrectInputLength { expected: usize, found: usize, name: String, span: Span },
#[error("Expected {expected} outputs for {name}, found {found}")]
IncorrectOutputLength { expected: usize, found: usize, name: String, span: Span },
#[error("Expected function id {expected}, found {found}")]
UnexpectedFunctionId { expected: u32, found: u32, span: Span },
}

impl ParserError {
Expand All @@ -1005,6 +1062,7 @@ impl ParserError {
InvalidInputBitSize { span, .. } => *span,
IncorrectInputLength { span, .. } => *span,
IncorrectOutputLength { span, .. } => *span,
UnexpectedFunctionId { span, .. } => *span,
}
}
}
67 changes: 66 additions & 1 deletion acvm-repo/acir/src/parser/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::circuit::Circuit;
use crate::{
circuit::{Circuit, Program},
parser::ParserError,
};

/// Trims leading whitespace from each line of the input string
#[cfg(test)]
Expand Down Expand Up @@ -37,6 +40,17 @@ fn assert_circuit_roundtrip(src: &str) {
}
}

fn assert_program_roundtrip(src: &str) {
let program = Program::from_str(src).unwrap();
let program = program.to_string();
let program = trim_leading_whitespace_from_lines(&program);
let src = trim_leading_whitespace_from_lines(src);
if program != src {
println!("Expected:\n~~~\n{src}\n~~~\nGot:\n~~~\n{program}\n~~~");
similar_asserts::assert_eq!(program, src);
}
}

#[test]
fn current_witness() {
let src = "
Expand Down Expand Up @@ -687,3 +701,54 @@ fn array_dynamic() {
";
assert_circuit_roundtrip(src);
}

#[test]
fn fold_basic() {
let src = "
func 0
current witness index : _2
private parameters indices : [_0]
public parameters indices : [_1]
return value indices : []
CALL func 1: PREDICATE: EXPR [ 1 ]
inputs: [_0, _1], outputs: [_2]

func 1
current witness index : _3
private parameters indices : [_0, _1]
public parameters indices : []
return value indices : [_2]
BRILLIG CALL func 0: inputs: [EXPR [ (1, _0) (-1, _1) 0 ]], outputs: [_3]
EXPR [ (1, _0, _3) (-1, _1, _3) -1 ]
EXPR [ (-1, _0) (1, _2) 0 ]
";
assert_program_roundtrip(src);
}

#[test]
fn fold_basic_mismatched_ids() {
let src = "
func 0
current witness index : _2
private parameters indices : [_0]
public parameters indices : [_1]
return value indices : []
CALL func 1: PREDICATE: EXPR [ 1 ]
inputs: [_0, _1], outputs: [_2]

func 2
current witness index : _3
private parameters indices : [_0, _1]
public parameters indices : []
return value indices : [_2]
BRILLIG CALL func 0: inputs: [EXPR [ (1, _0) (-1, _1) 0 ]], outputs: [_3]
EXPR [ (1, _0, _3) (-1, _1, _3) -1 ]
EXPR [ (-1, _0) (1, _2) 0 ]
";
let result = Program::from_str(src).err().unwrap();
let ParserError::UnexpectedFunctionId { expected, found, .. } = result.get_error() else {
panic!("Expected `UnexpectedFunctionId` error");
};
assert_eq!(expected, 1);
assert_eq!(found, 2);
}
4 changes: 4 additions & 0 deletions acvm-repo/acir/src/parser/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ pub(crate) enum Keyword {
CallData,
/// RETURNDATA
ReturnData,
/// func
Function,
}

impl Keyword {
Expand All @@ -142,6 +144,7 @@ impl Keyword {
"PREDICATE" => Keyword::Predicate,
"CALLDATA" => Keyword::CallData,
"RETURNDATA" => Keyword::ReturnData,
"func" => Keyword::Function,
_ => return None,
};
Some(Token::Keyword(keyword))
Expand Down Expand Up @@ -169,6 +172,7 @@ impl std::fmt::Display for Keyword {
Keyword::Predicate => write!(f, "PREDICATE"),
Keyword::CallData => write!(f, "CALLDATA"),
Keyword::ReturnData => write!(f, "RETURNDATA"),
Keyword::Function => write!(f, "func"),
}
}
}
Loading