1
0
Fork 0
mirror of synced 2025-09-24 04:40:05 +00:00

bring pest parser and AST into tree as crates, adjust testing

This commit is contained in:
schaeff 2019-06-03 10:22:59 +02:00
parent 20e12c10ea
commit 365d03be65
11 changed files with 1244 additions and 27 deletions

32
Cargo.lock generated
View file

@ -1477,7 +1477,7 @@ dependencies = [
"wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"zokrates_embed 0.1.0",
"zokrates_field 0.3.3",
"zokrates_pest_ast 0.1.0 (git+https://github.com/Schaeff/zokrates_pest_ast?branch=grammar-overhaul)",
"zokrates_pest_ast 0.1.0",
]
[[package]]
@ -1488,23 +1488,6 @@ dependencies = [
"sapling-crypto 0.0.4 (git+https://github.com/matterinc/sapling-crypto?tag=0.0.4)",
]
[[package]]
name = "zokrates_field"
version = "0.3.3"
source = "git+https://github.com/ZoKrates/ZoKrates#28fe6299d78f6fcdf76ce44f5f64abaaf6635683"
dependencies = [
"bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.5.0 (git+https://github.com/matterinc/ff?tag=0.5)",
"lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"pairing 0.16.2 (git+https://github.com/matterinc/pairing?tag=0.16.2)",
"serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zokrates_field"
version = "0.3.3"
@ -1530,10 +1513,10 @@ dependencies = [
]
[[package]]
name = "zokrates_pest"
name = "zokrates_parser"
version = "0.1.0"
source = "git+https://github.com/JacobEberhardt/ZoKratesGrammarSpecPEG#b5565a42b14d5ec2bd15975907e9d147972c5e98"
dependencies = [
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1541,14 +1524,14 @@ dependencies = [
[[package]]
name = "zokrates_pest_ast"
version = "0.1.0"
source = "git+https://github.com/Schaeff/zokrates_pest_ast?branch=grammar-overhaul#a5af535857f923fd219c13c6df03b759f313addb"
dependencies = [
"from-pest 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pest-ast 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"zokrates_field 0.3.3 (git+https://github.com/ZoKrates/ZoKrates)",
"zokrates_pest 0.1.0 (git+https://github.com/JacobEberhardt/ZoKratesGrammarSpecPEG)",
"zokrates_field 0.3.3",
"zokrates_parser 0.1.0",
]
[[package]]
@ -1740,6 +1723,3 @@ dependencies = [
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032"
"checksum zokrates_field 0.3.3 (git+https://github.com/ZoKrates/ZoKrates)" = "<none>"
"checksum zokrates_pest 0.1.0 (git+https://github.com/JacobEberhardt/ZoKratesGrammarSpecPEG)" = "<none>"
"checksum zokrates_pest_ast 0.1.0 (git+https://github.com/Schaeff/zokrates_pest_ast?branch=grammar-overhaul)" = "<none>"

View file

@ -29,7 +29,7 @@ bellman = { git = "https://github.com/matterinc/bellman", tag = "0.2.0" }
pairing = { git = "https://github.com/matterinc/pairing", tag = "0.16.2" }
ff = { git = 'https://github.com/matterinc/ff', features = ["derive"], tag = "0.5" }
zokrates_field = { version = "0.3.0", path = "../zokrates_field" }
zokrates_pest_ast = { git = "https://github.com/Schaeff/zokrates_pest_ast", branch = "grammar-overhaul" }
zokrates_pest_ast = { version = "0.1.0", path = "../zokrates_pest_ast" }
zokrates_embed = { path = "../zokrates_embed" }
rand = "0.4"
wasmi = { version = "0.4.2", optional = true }

56
zokrates_parser/.gitignore vendored Normal file
View file

@ -0,0 +1,56 @@
# Created by https://www.gitignore.io/api/rust,macos,visualstudiocode
# Edit at https://www.gitignore.io/?templates=rust,macos,visualstudiocode
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Rust ###
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/rust,macos,visualstudiocode

View file

@ -0,0 +1,12 @@
[package]
name = "zokrates_parser"
version = "0.1.0"
authors = ["JacobEberhardt <jacob.eberhardt@tu-berlin.de>"]
edition = "2018"
[dependencies]
pest = "2.0"
pest_derive = "2.0"
[dev-dependencies]
glob = "0.2"

12
zokrates_parser/README.md Normal file
View file

@ -0,0 +1,12 @@
# ZoKratesGrammarSpecPEG
[![CircleCI master](https://img.shields.io/circleci/project/github/JacobEberhardt/ZoKratesGrammarSpecPEG/master.svg?label=master)](https://circleci.com/gh/JacobEberhardt/ZoKratesGrammarSpecPEG/tree/master)
Formal grammar specification of the ZoKrates DSL in PEG (Pest).
This is experimental.
## Example folder
The example folder is copied from `zokrates_cli/examples` with the following changes:
- Remove error/wrong-syntax.code
- Remove error/unassigned.code

146
zokrates_parser/src/lib.rs Normal file
View file

@ -0,0 +1,146 @@
extern crate pest;
#[macro_use]
extern crate pest_derive;
use pest::error::Error;
use pest::iterators::Pairs;
use pest::Parser;
#[derive(Parser)]
#[grammar = "zokrates.pest"]
struct ZoKratesParser;
pub fn parse(input: &str) -> Result<Pairs<Rule>, Error<Rule>> {
ZoKratesParser::parse(Rule::file, input)
}
#[cfg(test)]
mod tests {
use super::*;
use pest::*;
mod examples {
use super::*;
#[test]
fn examples_dir() {
use glob::glob;
use std::fs;
use std::io::Read;
// Traverse all .code files in examples dir
for entry in
glob("../zokrates_cli/examples/**/*.code").expect("Failed to read glob pattern")
{
match entry {
Ok(path) => {
if path.to_str().unwrap().contains("error") {
continue;
}
println!("Parsing {:?}", path.display());
let mut file = fs::File::open(path).unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
assert!(ZoKratesParser::parse(Rule::file, &data).is_ok());
}
Err(e) => panic!("{:?}", e),
}
}
}
}
mod rules {
use super::*;
#[test]
fn parse_valid_identifier() {
parses_to! {
parser: ZoKratesParser,
input: "valididentifier_01",
rule: Rule::identifier,
tokens: [
identifier(0, 18)
]
};
}
#[test]
fn parse_parameter_list() {
parses_to! {
parser: ZoKratesParser,
input: "def foo(field a) -> (field, field): return 1
",
rule: Rule::function_definition,
tokens: [
function_definition(0, 45, [
identifier(4, 7),
// parameter_list is not created (silent rule)
parameter(8, 15, [
ty(8, 13, [
ty_basic(8, 13, [
ty_field(8, 13)
])
]),
identifier(14, 15)
]),
// type_list is not created (silent rule)
ty(21, 26, [
ty_basic(21, 26, [
ty_field(21, 26)
])
]),
ty(28, 33, [
ty_basic(28, 33, [
ty_field(28, 33)
])
]),
statement(36, 45, [
return_statement(36, 44, [
expression(43, 44, [
term(43, 44, [
primary_expression(43, 44, [
constant(43, 44)
])
])
])
])
])
])
]
};
}
#[test]
fn parse_invalid_identifier() {
fails_with! {
parser: ZoKratesParser,
input: "0_invalididentifier",
rule: Rule::identifier,
positives: vec![Rule::identifier],
negatives: vec![],
pos: 0
};
}
#[test]
fn parse_invalid_identifier_because_keyword() {
fails_with! {
parser: ZoKratesParser,
input: "endfor",
rule: Rule::identifier,
positives: vec![Rule::identifier],
negatives: vec![],
pos: 0
};
}
#[test]
fn parse_for_loop() {
let input = "for field i in 0..3 do \n c = c + a[i] \n endfor";
let parse = ZoKratesParser::parse(Rule::iteration_statement, input);
assert!(parse.is_ok());
}
}
}

View file

@ -0,0 +1,105 @@
/**
* ZoKrates Grammar
* Author: Jacob Eberhardt
*/
// TODO:
// exclude language keywords as identifiers: DONE
// Ignore linebreak after \
// Doc: associativity and precedence table for operators
file = { SOI ~ NEWLINE* ~ import_directive* ~ NEWLINE* ~ function_definition* ~ EOI }
import_directive = {"import" ~ "\"" ~ import_source ~ "\"" ~ ("as" ~ identifier)? ~ NEWLINE+}
import_source = @{(!"\"" ~ ANY)*}
function_definition = {"def" ~ identifier ~ "(" ~ parameter_list ~ ")" ~ "->" ~ "(" ~ type_list ~ ")" ~ ":" ~ NEWLINE* ~ statement* }
parameter_list = _{(parameter ~ ("," ~ parameter)*)?}
parameter = {vis? ~ ty ~ identifier}
// basic types
ty_field = {"field"}
ty_bool = {"bool"}
ty_basic = { ty_field | ty_bool }
// (unidimensional for now) arrays of (basic for now) types
ty_array = { ty_basic ~ ("[" ~ expression ~ "]") }
ty = { ty_array | ty_basic }
type_list = _{(ty ~ ("," ~ ty)*)?}
vis_private = {"private"}
vis_public = {"public"}
vis = { vis_private | vis_public }
// Statements
statement = { (return_statement // does not require subsequent newline
| (iteration_statement
| multi_assignment_statement // try this first as we want all assignments based on return of function calls match here and not later
| definition_statement
| assignment_statement
| expression_statement
) ~ NEWLINE
) ~ NEWLINE* }
iteration_statement = { "for" ~ ty ~ identifier ~ "in" ~ expression ~ ".." ~ expression ~ "do" ~ NEWLINE* ~ statement* ~ "endfor"}
return_statement = { "return" ~ expression_list}
multi_assignment_statement = { optionally_typed_identifier_list ~ "=" ~ identifier ~ "(" ~ expression_list ~ ")"} // This is very specific with regards to parsing. However, I think more generality is not needed here.
definition_statement = {ty ~ identifier ~ "=" ~ expression} // declare and assign, so only identifiers are allowed, unlike `assignment_statement`
assignment_statement = {assignee ~ "=" ~ expression } // TODO: Is this optimal? Can the left side be written more elegantly?
expression_statement = {expression}
optionally_typed_identifier_list = _{ optionally_typed_identifier ~ ("," ~ optionally_typed_identifier)* }
optionally_typed_identifier = { ty? ~ identifier }
// Expressions
expression_list = _{(expression ~ ("," ~ expression)*)?}
expression = { term ~ (op_binary ~ term)* }
term = { ("(" ~ expression ~ ")") | conditional_expression | postfix_expression | primary_expression | inline_array_expression | unary_expression }
conditional_expression = { "if" ~ expression ~ "then" ~ expression ~ "else" ~ expression ~ "fi"}
postfix_expression = { identifier ~ access+ } // we force there to be at least one access, otherwise this matches single identifiers. Not sure that's what we want.
access = { array_access | call_access }
array_access = { "[" ~ expression ~ "]" }
call_access = { "(" ~ expression_list ~ ")" }
primary_expression = { identifier
| constant
}
inline_array_expression = { "[" ~ expression_list ~ "]" }
unary_expression = { op_unary ~ term }
// End Expressions
assignee = { identifier ~ ("[" ~ expression ~ "]")* }
identifier = @{ ((!keyword ~ ASCII_ALPHA) | (keyword ~ (ASCII_ALPHANUMERIC | "_"))) ~ (ASCII_ALPHANUMERIC | "_")* }
constant = @{ "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
op_inclusive_or = {"||"}
op_exclusive_or = {"^"}
op_and = {"&&"}
op_equal = {"=="}
op_not_equal = {"!="}
op_lt = {"<"}
op_lte = {"<="}
op_gt = {">"}
op_gte = {">="}
op_add = {"+"}
op_sub = {"-"}
op_mul = {"*"}
op_div = {"/"}
op_pow = {"**"}
op_not = {"!"}
op_binary = _ { op_pow | op_inclusive_or | op_exclusive_or | op_and | op_equal | op_not_equal | op_lte | op_lt | op_gte | op_gt | op_add | op_sub | op_mul | op_div }
op_unary = { op_not }
WHITESPACE = _{ " " | "\t" | "\\" ~ NEWLINE}
COMMENT = _{ ("/*" ~ (!"*/" ~ ANY)* ~ "*/") | ("//" ~ (!NEWLINE ~ ANY)*) }
// TODO: Order by alphabet
keyword = @{"for" | "endfor" | "as" | "in" | "return" | "byte" | "field" | "bool" | "if" | "do" | "else" | "export" | "false" |
"def" | "for" | "import" | "uint" |
"in" | "public" | "private" | "return" |
"struct" | "true"
}

3
zokrates_pest_ast/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

View file

@ -0,0 +1,16 @@
[package]
name = "zokrates_pest_ast"
version = "0.1.0"
authors = ["schaeff <thibaut@schaeff.fr>"]
edition = "2018"
[dependencies]
zokrates_parser = { version = "0.1.0", path = "../zokrates_parser" }
zokrates_field = { version = "0.3.0", path = "../zokrates_field" }
pest = "2.0"
pest-ast = "0.3.3"
from-pest = "0.3.1"
lazy_static = "1.3.0"
[dev-dependencies]
glob = "0.2"

View file

@ -0,0 +1,6 @@
# zokrates_pest_ast
[![CircleCI master](https://img.shields.io/circleci/project/github/Schaeff/zokrates_pest_ast/master.svg?label=master)](https://circleci.com/gh/Schaeff/zokrates_pest_ast/tree/master)
ZoKrates AST generation based on pest output.
Experimental!

View file

@ -0,0 +1,881 @@
use from_pest::FromPest;
use pest::error::Error as PestError;
use pest::iterators::Pairs;
use std::fmt;
use zokrates_parser::parse;
use zokrates_parser::Rule;
#[macro_use]
extern crate lazy_static;
pub use ast::{
Access, ArrayAccess, ArrayType, AssertionStatement, Assignee, AssignmentStatement, BasicType,
BinaryExpression, BinaryOperator, CallAccess, ConstantExpression, DefinitionStatement,
Expression, File, Function, IdentifierExpression, ImportDirective, ImportSource,
InlineArrayExpression, IterationStatement, MultiAssignmentStatement, Parameter,
PostfixExpression, ReturnStatement, Statement, TernaryExpression, Type, UnaryExpression,
UnaryOperator, Visibility,
};
mod ast {
use from_pest::ConversionError;
use from_pest::FromPest;
use from_pest::Void;
use pest::iterators::{Pair, Pairs};
use pest::prec_climber::{Assoc, Operator, PrecClimber};
use pest::Span;
use pest_ast::FromPest;
use zokrates_parser::Rule;
lazy_static! {
static ref PREC_CLIMBER: PrecClimber<Rule> = build_precedence_climber();
}
fn build_precedence_climber() -> PrecClimber<Rule> {
PrecClimber::new(vec![
Operator::new(Rule::op_inclusive_or, Assoc::Left),
Operator::new(Rule::op_exclusive_or, Assoc::Left),
Operator::new(Rule::op_and, Assoc::Left),
Operator::new(Rule::op_equal, Assoc::Left)
| Operator::new(Rule::op_not_equal, Assoc::Left),
Operator::new(Rule::op_lte, Assoc::Left)
| Operator::new(Rule::op_gte, Assoc::Left)
| Operator::new(Rule::op_lt, Assoc::Left)
| Operator::new(Rule::op_gt, Assoc::Left),
Operator::new(Rule::op_add, Assoc::Left) | Operator::new(Rule::op_sub, Assoc::Left),
Operator::new(Rule::op_mul, Assoc::Left) | Operator::new(Rule::op_div, Assoc::Left),
Operator::new(Rule::op_pow, Assoc::Left),
])
}
// Create an Expression from left and right terms and an operator
// Precondition: `pair` MUST be a binary operator
fn infix_rule<'ast>(
lhs: Box<Expression<'ast>>,
pair: Pair<'ast, Rule>,
rhs: Box<Expression<'ast>>,
) -> Box<Expression<'ast>> {
// a + b spans from the start of a to the end of b
let (start, _) = lhs.span().clone().split();
let (_, end) = rhs.span().clone().split();
let span = start.span(&end);
Box::new(match pair.as_rule() {
Rule::op_add => Expression::binary(BinaryOperator::Add, lhs, rhs, span),
Rule::op_sub => Expression::binary(BinaryOperator::Sub, lhs, rhs, span),
Rule::op_mul => Expression::binary(BinaryOperator::Mul, lhs, rhs, span),
Rule::op_div => Expression::binary(BinaryOperator::Div, lhs, rhs, span),
Rule::op_pow => Expression::binary(BinaryOperator::Pow, lhs, rhs, span),
Rule::op_equal => Expression::binary(BinaryOperator::Eq, lhs, rhs, span),
Rule::op_not_equal => Expression::binary(BinaryOperator::NotEq, lhs, rhs, span),
Rule::op_lte => Expression::binary(BinaryOperator::Lte, lhs, rhs, span),
Rule::op_lt => Expression::binary(BinaryOperator::Lt, lhs, rhs, span),
Rule::op_gte => Expression::binary(BinaryOperator::Gte, lhs, rhs, span),
Rule::op_gt => Expression::binary(BinaryOperator::Gt, lhs, rhs, span),
Rule::op_inclusive_or => Expression::binary(BinaryOperator::Or, lhs, rhs, span),
Rule::op_exclusive_or => Expression::binary(BinaryOperator::Xor, lhs, rhs, span),
Rule::op_and => Expression::binary(BinaryOperator::And, lhs, rhs, span),
_ => unreachable!(),
})
}
// Create an Expression from an `expression`. `build_factor` turns each term into an `Expression` and `infix_rule` turns each (Expression, operator, Expression) into an Expression
pub fn climb(pair: Pair<Rule>) -> Box<Expression> {
PREC_CLIMBER.climb(pair.into_inner(), build_factor, infix_rule)
}
// Create an Expression from a `term`.
// Precondition: `pair` MUST be a term
fn build_factor(pair: Pair<Rule>) -> Box<Expression> {
Box::new(match pair.as_rule() {
// all factors are terms TODO rename factor to term?
Rule::term => {
// clone the pair to peek into what we should create
let clone = pair.clone();
// define the child pair
let next = clone.into_inner().next().unwrap();
match next.as_rule() {
// this happens when we have an expression in parentheses: it needs to be processed as another sequence of terms and operators
Rule::expression => Expression::from_pest(&mut pair.into_inner()).unwrap(),
Rule::conditional_expression => Expression::Ternary(
TernaryExpression::from_pest(&mut pair.into_inner()).unwrap(),
),
Rule::primary_expression => {
// maybe this could be simplified
let next = next.into_inner().next().unwrap();
match next.as_rule() {
Rule::constant => Expression::Constant(
ConstantExpression::from_pest(
&mut pair.into_inner().next().unwrap().into_inner(),
)
.unwrap(),
),
Rule::identifier => Expression::Identifier(
IdentifierExpression::from_pest(
&mut pair.into_inner().next().unwrap().into_inner(),
)
.unwrap(),
),
r => unreachable!("`primary_expression` should contain one of [`constant`, `identifier`], found {:#?}", r),
}
}
Rule::postfix_expression => Expression::Postfix(
PostfixExpression::from_pest(&mut pair.into_inner()).unwrap(),
),
Rule::inline_array_expression => Expression::InlineArray(
InlineArrayExpression::from_pest(&mut pair.into_inner()).unwrap(),
),
Rule::unary_expression => {
let span = next.as_span();
let mut inner = next.into_inner();
let op = match inner.next().unwrap().as_rule() {
Rule::op_unary => UnaryOperator::from_pest(&mut pair.into_inner().next().unwrap().into_inner()).unwrap(),
r => unreachable!("`unary_expression` should yield `op_unary`, found {:#?}", r)
};
let expression = build_factor(inner.next().unwrap());
Expression::Unary(UnaryExpression {
op,
expression,
span
})
},
r => unreachable!("`term` should contain one of [`expression`, `conditional_expression`, `primary_expression`, `postfix_expression`, `inline_array_expression`, `unary_expression`], found {:#?}", r)
}
}
r => unreachable!(
"`build_factor` can only be called on `term`, found {:#?}",
r
),
})
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::file))]
pub struct File<'ast> {
pub imports: Vec<ImportDirective<'ast>>,
pub functions: Vec<Function<'ast>>,
pub eoi: EOI,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::function_definition))]
pub struct Function<'ast> {
pub id: IdentifierExpression<'ast>,
pub parameters: Vec<Parameter<'ast>>,
pub returns: Vec<Type<'ast>>,
pub statements: Vec<Statement<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::import_directive))]
pub struct ImportDirective<'ast> {
pub source: ImportSource<'ast>,
pub alias: Option<IdentifierExpression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::import_source))]
pub struct ImportSource<'ast> {
#[pest_ast(outer(with(span_into_str)))]
pub value: String,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::ty))]
pub enum Type<'ast> {
Basic(BasicType<'ast>),
Array(ArrayType<'ast>),
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::ty_basic))]
pub enum BasicType<'ast> {
Field(FieldType),
Boolean(BooleanType<'ast>),
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::ty_field))]
pub struct FieldType {}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::ty_array))]
pub struct ArrayType<'ast> {
pub ty: BasicType<'ast>,
pub size: Expression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::ty_bool))]
pub struct BooleanType<'ast> {
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::parameter))]
pub struct Parameter<'ast> {
pub visibility: Option<Visibility>,
pub ty: Type<'ast>,
pub id: IdentifierExpression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::vis))]
pub enum Visibility {
Public(PublicVisibility),
Private(PrivateVisibility),
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::vis_public))]
pub struct PublicVisibility {}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::vis_private))]
pub struct PrivateVisibility {}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::statement))]
pub enum Statement<'ast> {
Return(ReturnStatement<'ast>),
Definition(DefinitionStatement<'ast>),
Assertion(AssertionStatement<'ast>),
Iteration(IterationStatement<'ast>),
Assignment(AssignmentStatement<'ast>),
MultiAssignment(MultiAssignmentStatement<'ast>),
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::definition_statement))]
pub struct DefinitionStatement<'ast> {
pub ty: Type<'ast>,
pub id: IdentifierExpression<'ast>,
pub expression: Expression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::assignment_statement))]
pub struct AssignmentStatement<'ast> {
pub assignee: Assignee<'ast>,
pub expression: Expression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::expression_statement))]
pub struct AssertionStatement<'ast> {
pub expression: Expression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::iteration_statement))]
pub struct IterationStatement<'ast> {
pub ty: Type<'ast>,
pub index: IdentifierExpression<'ast>,
pub from: Expression<'ast>,
pub to: Expression<'ast>,
pub statements: Vec<Statement<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::multi_assignment_statement))]
pub struct MultiAssignmentStatement<'ast> {
pub lhs: Vec<OptionallyTypedIdentifier<'ast>>,
pub function_id: IdentifierExpression<'ast>,
pub arguments: Vec<Expression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::return_statement))]
pub struct ReturnStatement<'ast> {
pub expressions: Vec<Expression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum BinaryOperator {
Xor,
Or,
And,
Add,
Sub,
Mul,
Div,
Eq,
NotEq,
Lt,
Gt,
Lte,
Gte,
Pow,
}
#[derive(Debug, PartialEq, FromPest, Clone)]
#[pest_ast(rule(Rule::op_unary))]
pub enum UnaryOperator<'ast> {
Not(Not<'ast>),
}
#[derive(Debug, PartialEq, FromPest, Clone)]
#[pest_ast(rule(Rule::op_not))]
pub struct Not<'ast> {
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Expression<'ast> {
Ternary(TernaryExpression<'ast>),
Binary(BinaryExpression<'ast>),
Postfix(PostfixExpression<'ast>),
Identifier(IdentifierExpression<'ast>),
Constant(ConstantExpression<'ast>),
InlineArray(InlineArrayExpression<'ast>),
Unary(UnaryExpression<'ast>),
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::postfix_expression))]
pub struct PostfixExpression<'ast> {
pub id: IdentifierExpression<'ast>,
pub access: Vec<Access<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::unary_expression))]
pub struct UnaryExpression<'ast> {
pub op: UnaryOperator<'ast>,
pub expression: Box<Expression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::inline_array_expression))]
pub struct InlineArrayExpression<'ast> {
pub expressions: Vec<Expression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::optionally_typed_identifier))]
pub struct OptionallyTypedIdentifier<'ast> {
pub ty: Option<Type<'ast>>,
pub id: IdentifierExpression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::access))]
pub enum Access<'ast> {
Call(CallAccess<'ast>),
Select(ArrayAccess<'ast>),
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::call_access))]
pub struct CallAccess<'ast> {
pub expressions: Vec<Expression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::array_access))]
pub struct ArrayAccess<'ast> {
pub expression: Expression<'ast>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct BinaryExpression<'ast> {
pub op: BinaryOperator,
pub left: Box<Expression<'ast>>,
pub right: Box<Expression<'ast>>,
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::conditional_expression))]
pub struct TernaryExpression<'ast> {
pub first: Box<Expression<'ast>>,
pub second: Box<Expression<'ast>>,
pub third: Box<Expression<'ast>>,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
impl<'ast> Expression<'ast> {
pub fn ternary(
first: Box<Expression<'ast>>,
second: Box<Expression<'ast>>,
third: Box<Expression<'ast>>,
span: Span<'ast>,
) -> Self {
Expression::Ternary(TernaryExpression {
first,
second,
third,
span,
})
}
pub fn binary(
op: BinaryOperator,
left: Box<Expression<'ast>>,
right: Box<Expression<'ast>>,
span: Span<'ast>,
) -> Self {
Expression::Binary(BinaryExpression {
op,
left,
right,
span,
})
}
pub fn span(&self) -> &Span<'ast> {
match self {
Expression::Binary(b) => &b.span,
Expression::Identifier(i) => &i.span,
Expression::Constant(c) => &c.span,
Expression::Ternary(t) => &t.span,
Expression::Postfix(p) => &p.span,
Expression::InlineArray(a) => &a.span,
Expression::Unary(u) => &u.span,
}
}
}
impl<'ast> FromPest<'ast> for Expression<'ast> {
type Rule = Rule;
type FatalError = Void;
// We implement AST creation manually here for Expression
// `pest` should yield an `expression` which we can generate AST with, based on precedence rules
fn from_pest(pest: &mut Pairs<'ast, Rule>) -> Result<Self, ConversionError<Void>> {
// get a clone to "try" to match
let mut clone = pest.clone();
// advance by one pair in the clone, if none error out, `pest` is still the original
let pair = clone.next().ok_or(::from_pest::ConversionError::NoMatch)?;
// this should be an expression
match pair.as_rule() {
Rule::expression => {
// we can replace `pest` with the clone we tried with and got pairs from to create the AST
*pest = clone;
Ok(*climb(pair))
}
_ => Err(ConversionError::NoMatch),
}
}
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::constant))]
pub struct ConstantExpression<'ast> {
#[pest_ast(outer(with(span_into_str)))]
pub value: String,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::identifier))]
pub struct IdentifierExpression<'ast> {
#[pest_ast(outer(with(span_into_str)))]
pub value: String,
#[pest_ast(outer())]
pub span: Span<'ast>,
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::assignee))]
pub struct Assignee<'ast> {
pub id: IdentifierExpression<'ast>, // a
pub indices: Vec<Expression<'ast>>, // [42 + x][31][7]
#[pest_ast(outer())]
pub span: Span<'ast>,
}
fn span_into_str(span: Span) -> String {
span.as_str().to_string()
}
#[derive(Debug, FromPest, PartialEq, Clone)]
#[pest_ast(rule(Rule::EOI))]
pub struct EOI;
}
struct Prog<'ast>(ast::File<'ast>);
impl<'ast> From<Pairs<'ast, Rule>> for Prog<'ast> {
fn from(mut pairs: Pairs<'ast, Rule>) -> Prog<'ast> {
Prog(ast::File::from_pest(&mut pairs).unwrap())
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct Error(PestError<Rule>);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub fn generate_ast(input: &str) -> Result<ast::File, Error> {
let parse_tree = parse(input).map_err(|e| Error(e))?;
Ok(Prog::from(parse_tree).0)
}
#[cfg(test)]
mod tests {
use super::ast::*;
use super::*;
use pest::Span;
#[test]
fn examples() {
use glob::glob;
use std::fs;
use std::io::Read;
// Traverse all .code files in examples dir
for entry in
glob("../zokrates_cli/examples/**/*.code").expect("Failed to read glob pattern")
{
match entry {
Ok(path) => {
if path.to_str().unwrap().contains("error") {
continue;
}
println!("Parsing {:?}", path.display());
let mut file = fs::File::open(path).unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
let _res = generate_ast(&data).unwrap();
}
Err(e) => println!("{:?}", e),
}
}
}
impl<'ast> Expression<'ast> {
pub fn add(left: Expression<'ast>, right: Expression<'ast>, span: Span<'ast>) -> Self {
Self::binary(BinaryOperator::Add, Box::new(left), Box::new(right), span)
}
pub fn mul(left: Expression<'ast>, right: Expression<'ast>, span: Span<'ast>) -> Self {
Self::binary(BinaryOperator::Mul, Box::new(left), Box::new(right), span)
}
pub fn pow(left: Expression<'ast>, right: Expression<'ast>, span: Span<'ast>) -> Self {
Self::binary(BinaryOperator::Pow, Box::new(left), Box::new(right), span)
}
pub fn if_else(
condition: Expression<'ast>,
consequence: Expression<'ast>,
alternative: Expression<'ast>,
span: Span<'ast>,
) -> Self {
Self::ternary(
Box::new(condition),
Box::new(consequence),
Box::new(alternative),
span,
)
}
}
#[test]
fn one_plus_one() {
let source = r#"import "foo"
def main() -> (field): return 1 + 1
"#;
assert_eq!(
generate_ast(&source),
Ok(File {
functions: vec![Function {
id: IdentifierExpression {
value: String::from("main"),
span: Span::new(&source, 33, 37).unwrap()
},
parameters: vec![],
returns: vec![Type::Basic(BasicType::Field(FieldType {}))],
statements: vec![Statement::Return(ReturnStatement {
expressions: vec![Expression::add(
Expression::Constant(ConstantExpression {
value: String::from("1"),
span: Span::new(&source, 59, 60).unwrap()
}),
Expression::Constant(ConstantExpression {
value: String::from("1"),
span: Span::new(&source, 63, 64).unwrap()
}),
Span::new(&source, 59, 64).unwrap()
)],
span: Span::new(&source, 52, 64).unwrap(),
})],
span: Span::new(&source, 29, source.len()).unwrap(),
}],
imports: vec![ImportDirective {
source: ImportSource {
value: String::from("foo"),
span: Span::new(&source, 8, 11).unwrap()
},
alias: None,
span: Span::new(&source, 0, 29).unwrap()
}],
eoi: EOI {},
span: Span::new(&source, 0, 65).unwrap()
})
);
}
#[test]
fn precedence() {
let source = r#"import "foo"
def main() -> (field): return 1 + 2 * 3 ** 4
"#;
assert_eq!(
generate_ast(&source),
Ok(File {
functions: vec![Function {
id: IdentifierExpression {
value: String::from("main"),
span: Span::new(&source, 33, 37).unwrap()
},
parameters: vec![],
returns: vec![Type::Basic(BasicType::Field(FieldType {}))],
statements: vec![Statement::Return(ReturnStatement {
expressions: vec![Expression::add(
Expression::Constant(ConstantExpression {
value: String::from("1"),
span: Span::new(&source, 59, 60).unwrap()
}),
Expression::mul(
Expression::Constant(ConstantExpression {
value: String::from("2"),
span: Span::new(&source, 63, 64).unwrap()
}),
Expression::pow(
Expression::Constant(ConstantExpression {
value: String::from("3"),
span: Span::new(&source, 67, 68).unwrap()
}),
Expression::Constant(ConstantExpression {
value: String::from("4"),
span: Span::new(&source, 72, 73).unwrap()
}),
Span::new(&source, 67, 73).unwrap()
),
Span::new(&source, 63, 73).unwrap()
),
Span::new(&source, 59, 73).unwrap()
)],
span: Span::new(&source, 52, 73).unwrap(),
})],
span: Span::new(&source, 29, 74).unwrap(),
}],
imports: vec![ImportDirective {
source: ImportSource {
value: String::from("foo"),
span: Span::new(&source, 8, 11).unwrap()
},
alias: None,
span: Span::new(&source, 0, 29).unwrap()
}],
eoi: EOI {},
span: Span::new(&source, 0, 74).unwrap()
})
);
}
#[test]
fn ternary() {
let source = r#"import "foo"
def main() -> (field): return if 1 then 2 else 3 fi
"#;
assert_eq!(
generate_ast(&source),
Ok(File {
functions: vec![Function {
id: IdentifierExpression {
value: String::from("main"),
span: Span::new(&source, 33, 37).unwrap()
},
parameters: vec![],
returns: vec![Type::Basic(BasicType::Field(FieldType {}))],
statements: vec![Statement::Return(ReturnStatement {
expressions: vec![Expression::if_else(
Expression::Constant(ConstantExpression {
value: String::from("1"),
span: Span::new(&source, 62, 63).unwrap()
}),
Expression::Constant(ConstantExpression {
value: String::from("2"),
span: Span::new(&source, 69, 70).unwrap()
}),
Expression::Constant(ConstantExpression {
value: String::from("3"),
span: Span::new(&source, 76, 77).unwrap()
}),
Span::new(&source, 59, 80).unwrap()
)],
span: Span::new(&source, 52, 80).unwrap(),
})],
span: Span::new(&source, 29, 81).unwrap(),
}],
imports: vec![ImportDirective {
source: ImportSource {
value: String::from("foo"),
span: Span::new(&source, 8, 11).unwrap()
},
alias: None,
span: Span::new(&source, 0, 29).unwrap()
}],
eoi: EOI {},
span: Span::new(&source, 0, 81).unwrap()
})
);
}
#[test]
fn parentheses() {
let source = r#"def main() -> (field): return (1)
"#;
assert_eq!(
generate_ast(&source),
Ok(File {
functions: vec![Function {
id: IdentifierExpression {
value: String::from("main"),
span: Span::new(&source, 4, 8).unwrap()
},
parameters: vec![],
returns: vec![Type::Basic(BasicType::Field(FieldType {}))],
statements: vec![Statement::Return(ReturnStatement {
expressions: vec![Expression::Constant(ConstantExpression {
value: String::from("1"),
span: Span::new(&source, 31, 32).unwrap()
})],
span: Span::new(&source, 23, 33).unwrap(),
})],
span: Span::new(&source, 0, 34).unwrap(),
}],
imports: vec![],
eoi: EOI {},
span: Span::new(&source, 0, 34).unwrap()
})
);
}
#[test]
fn multidef() {
let source = r#"def main() -> (field): field a, b = foo(1, 2 + 3)
"#;
assert_eq!(
generate_ast(&source),
Ok(File {
functions: vec![Function {
id: IdentifierExpression {
value: String::from("main"),
span: Span::new(&source, 4, 8).unwrap()
},
parameters: vec![],
returns: vec![Type::Basic(BasicType::Field(FieldType {}))],
statements: vec![Statement::MultiAssignment(MultiAssignmentStatement {
function_id: IdentifierExpression {
value: String::from("foo"),
span: Span::new(&source, 36, 39).unwrap()
},
lhs: vec![
OptionallyTypedIdentifier {
ty: Some(Type::Basic(BasicType::Field(FieldType {}))),
id: IdentifierExpression {
value: String::from("a"),
span: Span::new(&source, 29, 30).unwrap(),
},
span: Span::new(&source, 23, 30).unwrap()
},
OptionallyTypedIdentifier {
ty: None,
id: IdentifierExpression {
value: String::from("b"),
span: Span::new(&source, 32, 33).unwrap(),
},
span: Span::new(&source, 32, 33).unwrap()
},
],
arguments: vec![
Expression::Constant(ConstantExpression {
value: String::from("1"),
span: Span::new(&source, 40, 41).unwrap()
}),
Expression::add(
Expression::Constant(ConstantExpression {
value: String::from("2"),
span: Span::new(&source, 43, 44).unwrap()
}),
Expression::Constant(ConstantExpression {
value: String::from("3"),
span: Span::new(&source, 47, 48).unwrap()
}),
Span::new(&source, 43, 48).unwrap()
),
],
span: Span::new(&source, 23, 49).unwrap()
})],
span: Span::new(&source, 0, 50).unwrap(),
}],
imports: vec![],
eoi: EOI {},
span: Span::new(&source, 0, 50).unwrap()
})
);
}
#[test]
fn playground() {
let source = r#"import "heyman" as yo
def main(private field[23] a) -> (bool[234 + 6]):
field a = 1
a[32 + x][55] = y
for field i in 0..3 do
a == 1 + 2 + 3+ 4+ 5+ 6+ 6+ 7+ 8 + 4+ 5+ 3+ 4+ 2+ 3
endfor
a == 1
return a
"#;
let res = generate_ast(&source);
println!("{:#?}", generate_ast(&source));
assert!(res.is_ok());
}
}