bring pest parser and AST into tree as crates, adjust testing
This commit is contained in:
parent
20e12c10ea
commit
365d03be65
11 changed files with 1244 additions and 27 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
@ -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>"
|
||||
|
|
|
@ -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
56
zokrates_parser/.gitignore
vendored
Normal 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
|
12
zokrates_parser/Cargo.toml
Normal file
12
zokrates_parser/Cargo.toml
Normal 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
12
zokrates_parser/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# ZoKratesGrammarSpecPEG
|
||||
[](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
146
zokrates_parser/src/lib.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
105
zokrates_parser/src/zokrates.pest
Normal file
105
zokrates_parser/src/zokrates.pest
Normal 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
3
zokrates_pest_ast/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
16
zokrates_pest_ast/Cargo.toml
Normal file
16
zokrates_pest_ast/Cargo.toml
Normal 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"
|
6
zokrates_pest_ast/README.md
Normal file
6
zokrates_pest_ast/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# zokrates_pest_ast
|
||||
[](https://circleci.com/gh/Schaeff/zokrates_pest_ast/tree/master)
|
||||
|
||||
ZoKrates AST generation based on pest output.
|
||||
|
||||
Experimental!
|
881
zokrates_pest_ast/src/lib.rs
Normal file
881
zokrates_pest_ast/src/lib.rs
Normal 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());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue