Initial commit.

This commit is contained in:
Fredrik Dahlgren
2022-03-10 13:38:24 +01:00
commit 240f38cd46
44 changed files with 5116 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1078
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

7
Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[workspace]
members = [
"cli",
"parser",
"program_analysis",
"program_structure"
]

13
cli/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "circom-spectacles"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
log = "0.4"
parser = { path = "../parser" }
pretty_env_logger = "0.4"
program_analysis = { path = "../program_analysis" }
program_structure = { path = "../program_structure" }
structopt = "0.3"

55
cli/src/main.rs Normal file
View File

@@ -0,0 +1,55 @@
use anyhow::{anyhow, Result};
use log::info;
use pretty_env_logger;
use structopt::StructOpt;
use parser;
use program_analysis::shadowing_analysis::ShadowingAnalysis;
use program_structure::error_definition::Report;
use program_structure::program_archive::ProgramArchive;
const CIRCOM_VERSION: &str = "2.0.3";
#[derive(StructOpt)]
/// Analyze Circom programs
struct CLI {
/// Initial iput file
#[structopt(name = "input")]
input_file: String,
/// Output file (defaults to stdout)
#[structopt(name = "output")]
output_file: Option<String>,
/// Expected compiler version
#[structopt(long, short)]
compiler_version: Option<String>,
}
fn parse_project(
initial_file: &str,
compiler_version: Option<String>,
) -> Result<ProgramArchive> {
let compiler_version = &compiler_version.unwrap_or(CIRCOM_VERSION.to_string());
match parser::run_parser(initial_file.to_string(), compiler_version) {
Result::Err((files, reports)) => {
Report::print_reports(&reports, &files);
Err(anyhow!("failed to parse {}", initial_file))
}
Result::Ok((program, warnings)) => {
Report::print_reports(&warnings, &program.file_library);
Ok(program)
}
}
}
fn main() -> Result<()> {
pretty_env_logger::init();
let options = CLI::from_args();
let program = parse_project(&options.input_file, options.compiler_version)?;
let mut analysis = ShadowingAnalysis::new();
let reports = analysis.run(&program);
Report::print_reports(&reports, &program.file_library);
Ok(())
}

View File

@@ -0,0 +1,21 @@
pragma circom 2.0.0;
template ToBits(n) {
signal input in;
signal output out[n];
var power = 1;
var result = 0;
for (var i = 0; i < n; i++) {
// This declaration shadows the outer declaration of power.
var power = 2;
out[i] <-- (in >> i) & 1;
out[i] * (out[i] - 1) === 0;
result += out[i] * power;
power = power + power;
}
// This declaration shadows the previous declaration of power.
var power = 3;
result === in;
}
component main {public [in]} = ToBits(256);

View File

@@ -0,0 +1,19 @@
pragma circom 2.0.0;
template ToBits(n) {
signal input in;
signal output out[n];
var value = 0;
var power = 1;
var result = 0;
for (var i = 0; i < n; i++) {
out[i] <-- (in >> i) & 1;
// out[i] * (out[i] - 1) === 0;
result += out[i] * power;
power = power + power;
}
// The output is unconstrained.
// result === in;
}
component main {public [in]} = ToBits(256);

View File

@@ -0,0 +1,18 @@
pragma circom 2.0.0;
template ToBits(n) {
signal input in;
signal output out[n];
var value = 0; // The variable value is unused.
var power = 1;
var result = 0;
for (var i = 0; i < n; i++) {
out[i] <-- (in >> i) & 1;
out[i] * (out[i] - 1) === 0;
result += out[i] * power;
power = power + power;
}
result === in;
}
component main {public [in]} = ToBits(256);

23
parser/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "parser"
version = "2.0.1"
authors = ["Hermenegildo <hermegar@ucm.es>"]
edition = "2018"
build = "build.rs"
[build-dependencies]
rustc-hex = "2.0.1"
lalrpop = { version = "0.18.1", features = ["lexer"] }
num-bigint-dig = "0.6.0"
num-traits = "0.2.6"
[dependencies]
program_structure = {path = "../program_structure"}
lalrpop = { version = "0.18.1", features = ["lexer"] }
lalrpop-util = "0.18.1"
regex = "1.1.2"
rustc-hex = "2.0.1"
num-bigint-dig = "0.6.0"
num-traits = "0.2.6"
serde = "1.0.82"
serde_derive = "1.0.91"

5
parser/build.rs Normal file
View File

@@ -0,0 +1,5 @@
extern crate lalrpop;
fn main() {
lalrpop::process_root().unwrap();
}

87
parser/src/errors.rs Normal file
View File

@@ -0,0 +1,87 @@
use program_structure::error_code::ReportCode;
use program_structure::error_definition::Report;
use program_structure::file_definition::{FileID, FileLocation};
use program_structure::abstract_syntax_tree::ast::Version;
pub struct UnclosedCommentError {
pub location: FileLocation,
pub file_id: FileID,
}
impl UnclosedCommentError {
pub fn produce_report(error: Self) -> Report {
let mut report = Report::error(format!("unterminated /* */"), ReportCode::ParseFail);
report.add_primary(error.location, error.file_id, format!("Comment starts here"));
report
}
}
pub struct ParsingError {
pub location: FileLocation,
pub file_id: FileID,
pub msg: String,
}
impl ParsingError {
pub fn produce_report(error: Self) -> Report {
let mut report = Report::error(error.msg, ReportCode::ParseFail);
report.add_primary(error.location, error.file_id, format!("Invalid syntax"));
report
}
}
pub struct FileOsError {
pub path: String,
}
impl FileOsError {
pub fn produce_report(error: Self) -> Report {
Report::error(format!("Could not open file {}", error.path), ReportCode::ParseFail)
}
}
pub struct NoMainError;
impl NoMainError {
pub fn produce_report() -> Report {
Report::error(
format!("No main specified in the project structure"),
ReportCode::NoMainFoundInProject,
)
}
}
pub struct MultipleMainError;
impl MultipleMainError {
pub fn produce_report() -> Report {
Report::error(
format!("Multiple main components in the project structure"),
ReportCode::MultipleMainInComponent,
)
}
}
pub struct CompilerVersionError{
pub path: String,
pub required_version: Version,
pub version: Version,
}
impl CompilerVersionError {
pub fn produce_report(error: Self) -> Report {
Report::error(
format!("File {} requires pragma version {:?} that is not supported by the compiler (version {:?})", error.path, error.required_version, error.version ),
ReportCode::CompilerVersionError,
)
}
}
pub struct NoCompilerVersionWarning{
pub path: String,
pub version: Version,
}
impl NoCompilerVersionWarning {
pub fn produce_report(error: Self) -> Report {
Report::warning(
format!("File {} does not include pragma version. Assuming pragma version {:?}", error.path, error.version),
ReportCode::NoCompilerVersionWarning,
)
}
}

View File

@@ -0,0 +1,47 @@
use super::errors::FileOsError;
use program_structure::error_definition::Report;
use std::collections::HashSet;
use std::path::PathBuf;
pub struct FileStack {
current_location: PathBuf,
black_paths: HashSet<PathBuf>,
stack: Vec<PathBuf>,
}
impl FileStack {
pub fn new(src: PathBuf) -> FileStack {
let mut location = src.clone();
location.pop();
FileStack { current_location: location, black_paths: HashSet::new(), stack: vec![src] }
}
pub fn add_include(f_stack: &mut FileStack, path: String) -> Result<(), Report> {
let mut crr = f_stack.current_location.clone();
crr.push(path.clone());
let path = std::fs::canonicalize(crr)
.map_err(|_| FileOsError { path: path.clone() })
.map_err(|e| FileOsError::produce_report(e))?;
if !f_stack.black_paths.contains(&path) {
f_stack.stack.push(path);
}
Ok(())
}
pub fn take_next(f_stack: &mut FileStack) -> Option<PathBuf> {
loop {
match f_stack.stack.pop() {
None => {
break None;
}
Some(file) if !f_stack.black_paths.contains(&file) => {
f_stack.current_location = file.clone();
f_stack.current_location.pop();
f_stack.black_paths.insert(file.clone());
break Some(file);
}
_ => {}
}
}
}
}

525
parser/src/lang.lalrpop Normal file
View File

@@ -0,0 +1,525 @@
use num_bigint::BigInt;
use program_structure::statement_builders::*;
use program_structure::expression_builders::*;
use program_structure::ast::*;
use program_structure::ast_shortcuts::{self,Symbol};
use std::str::FromStr;
grammar;
// ====================================================================
// Body
// ====================================================================
// A identifier list is a comma separated list of identifiers
IdentifierListDef : Vec<String> = {
<v:(<IDENTIFIER> ",")*> <e:IDENTIFIER> => {
let mut v = v;
v.push(e);
v
}
};
// Pragma is included at the start of the file.
// Their structure is the following: pragma circom "version of the compiler"
ParsePragma : Version = { // maybe change to usize instead of BigInt
"pragma circom" <version: Version> ";"
=> version,
};
// Includes are added at the start of the file.
// Their structure is the following: #include "path to the file"
ParseInclude : String = {
"include" <file: STRING> ";"
=> file,
};
// Parsing a program requires:
// Parsing the "pragma" instruction, if there is one
// Parsing "includes" instructions, if there is anyone,
// Parsing function and template definitions,
// Parsing the declaration of the main component
pub ParseAst : AST = {
<s:@L> <pragma: ParsePragma> <includes: ParseInclude*> <definitions: ParseDefinition*> <main: ParseMainComponent> <e:@R>
=> AST::new(Meta::new(s,e), Option::Some(pragma), includes,definitions,Option::Some(main)),
<s:@L> <includes: ParseInclude*> <definitions: ParseDefinition*> <main: ParseMainComponent> <e:@R>
=> AST::new(Meta::new(s,e),Option::None, includes,definitions,Option::Some(main)),
<s:@L> <pragma: ParsePragma> <includes: ParseInclude*> <definitions: ParseDefinition*> <e:@R>
=> AST::new(Meta::new(s,e), Option::Some(pragma), includes,definitions,Option::None),
<s:@L> <includes: ParseInclude*> <definitions: ParseDefinition*> <e:@R>
=> AST::new(Meta::new(s,e), Option::None,includes,definitions,Option::None),
};
// ====================================================================
// Definitions
// ====================================================================
// The private list of the main component stands for the
// list of private input signals
ParsePublicList : Vec<String> = {
"{" "public" "[" <id: IdentifierListDef> "]" "}" => id,
};
pub ParseMainComponent : MainComponent = {
<s:@L> "component" "main" <public_list: ParsePublicList?> "=" <init: ParseExpression> ";" <e:@L>
=> match public_list {
None => build_main_component(Vec::new(),init),
Some(list) => build_main_component(list,init)
},
};
pub ParseDefinition : Definition = {
<s:@L> "function" <name: IDENTIFIER> "(" <args:@L> <arg_names: IdentifierListDef?> <arge:@R> ")" <body: ParseBlock> <e:@R>
=> match arg_names {
None
=> build_function(Meta::new(s,e),name,Vec::new(),args..arge,body),
Some(a)
=> build_function(Meta::new(s,e),name,a,args..arge,body),
},
<s:@L> "template" <parallel: "parallel"?> <name: IDENTIFIER> "(" <args:@L> <arg_names: IdentifierListDef?> <arge:@R> ")" <body: ParseBlock> <e:@R>
=> match arg_names {
None
=> build_template(Meta::new(s,e), name, Vec::new(), args..arge, body, parallel.is_some()),
Some(a)
=> build_template(Meta::new(s,e), name, a, args..arge, body, parallel.is_some()),
},
};
// ====================================================================
// VariableDefinitions
// ====================================================================
ParseElementType : SignalElementType = {
"FieldElement" => SignalElementType::FieldElement,
"Binary" => SignalElementType::Binary,
};
ParseSignalType: SignalType = {
"input" => SignalType::Input,
"output" => SignalType::Output
};
SignalHeader : VariableType = {
"signal" <element_type: (":" <ParseElementType>)?> <signal_type: ParseSignalType?>
=> {
let e = match element_type {
None => SignalElementType::FieldElement,
Some(t) => t,
};
let s = match signal_type {
None => SignalType::Intermediate,
Some(st) => st,
};
VariableType::Signal(s,e)
}
};
// ====================================================================
// Statements
// ====================================================================
// A Initialization is either just the name of a variable or
// the name followed by a expression that initialices the variable.
SimpleSymbol : Symbol = {
<name:IDENTIFIER> <dims:ParseArrayAcc*>
=> Symbol {
name,
is_array: dims,
init: Option::None,
},
}
ComplexSymbol : Symbol = {
<name:IDENTIFIER> <dims:ParseArrayAcc*> "=" <rhe: ParseExpression>
=> Symbol {
name,
is_array: dims,
init: Option::Some(rhe),
},
};
SomeSymbol : Symbol = {
ComplexSymbol,
SimpleSymbol,
}
// A declaration is the definition of a type followed by the initialization
ParseDeclaration : Statement = {
<s:@L> "var" <symbols:(<SomeSymbol> ",")*> <symbol: SomeSymbol> <e:@R> => {
let mut symbols = symbols;
let meta = Meta::new(s,e);
let xtype = VariableType::Var;
symbols.push(symbol);
ast_shortcuts::split_declaration_into_single_nodes(meta,xtype,symbols)
},
<s:@L> "component" <symbols:(<SomeSymbol> ",")*> <symbol: SomeSymbol> <e:@R> => {
let mut symbols = symbols;
let meta = Meta::new(s,e);
let xtype = VariableType::Component;
symbols.push(symbol);
ast_shortcuts::split_declaration_into_single_nodes(meta,xtype,symbols)
},
<s:@L><xtype: SignalHeader> <symbols:(<SimpleSymbol> ",")*> <symbol: SimpleSymbol> <e:@R>
=> {
let mut symbols = symbols;
let meta = Meta::new(s,e);
symbols.push(symbol);
ast_shortcuts::split_declaration_into_single_nodes(meta,xtype,symbols)
},
};
ParseSubstitution : Statement = {
<s:@L> <variable: ParseVariable> <op: ParseAssignOp> <rhe: ParseExpression> <e:@R>
=> {let (name,access) = variable;
build_substitution(Meta::new(s,e),name,access,op,rhe)
},
<s:@L> <lhe: ParseExpression> "-->" <variable: ParseVariable> <e:@R>
=> {let (name,access) = variable;
build_substitution(Meta::new(s,e),name,access,AssignOp::AssignSignal,lhe)
},
<s:@L> <lhe: ParseExpression> "==>" <variable: ParseVariable> <e:@R>
=> {let (name,access) = variable;
build_substitution(Meta::new(s,e),name,access,AssignOp::AssignConstraintSignal,lhe)
},
<s:@L> <variable: ParseVariable> "\\=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::IntDiv,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "**=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Pow,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "+=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Add,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "-=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Sub,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "*=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Mul,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "/=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Div,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "%=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Mod,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "<<=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::ShiftL,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> ">>=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::ShiftR,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "&=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::BitAnd,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "|=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::BitOr,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "^=" <rhe: ParseExpression> <e:@R>
=> ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::BitXor,Meta::new(s,e),variable,rhe),
<s:@L> <variable: ParseVariable> "++" <e:@R>
=> ast_shortcuts::plusplus(Meta::new(s,e),variable),
<s:@L> <variable: ParseVariable> "--" <e:@R>
=> ast_shortcuts::subsub(Meta::new(s,e),variable),
};
ParseBlock : Statement = {
<s:@L> "{" <stmts :ParseStatement3*> "}" <e:@R>
=> build_block(Meta::new(s,e),stmts),
};
pub ParseStatement : Statement = {
ParseStatement0
};
ParseElse<StmtLevel> : Statement = {
"else" <else_case: StmtLevel> => else_case,
};
ParseStatement0 : Statement = {
ParseStmt0NB,
ParseStatement1
};
ParseStmt0NB : Statement = {
<s:@L> "if" "(" <cond: ParseExpression> ")" <if_case: ParseStmt0NB> <e:@R>
=> build_conditional_block(Meta::new(s,e),cond,if_case,Option::None),
<s:@L> "if" "(" <cond: ParseExpression> ")" <if_case: ParseStatement1> <e:@R>
=> build_conditional_block(Meta::new(s,e),cond,if_case,Option::None),
<s:@L> "if" "(" <cond: ParseExpression> ")" <if_case: ParseStatement1> <else_case: ParseElse<ParseStmt0NB>><e:@R>
=> build_conditional_block(Meta::new(s,e),cond,if_case,Option::Some(else_case)),
};
ParseStatement1 : Statement = {
<s:@L> "if" "(" <cond: ParseExpression> ")" <if_case: ParseStatement1> <else_case: ParseElse<ParseStatement1>><e:@R>
=> build_conditional_block(Meta::new(s,e),cond,if_case,Option::Some(else_case)),
ParseStatement2
};
ParseStatement2 : Statement = {
<s:@L> "for" "(" <init: ParseDeclaration> ";" <cond: ParseExpression> ";" <step: ParseSubstitution> ")" <body: ParseStatement2> <e:@R>
=> ast_shortcuts::for_into_while(Meta::new(s,e),init,cond,step,body),
<s:@L> "for" "(" <init: ParseSubstitution> ";" <cond: ParseExpression> ";" <step: ParseSubstitution> ")" <body: ParseStatement2> <e:@R>
=> ast_shortcuts::for_into_while(Meta::new(s,e),init,cond,step,body),
<s:@L>"while" "(" <cond: ParseExpression> ")" <stmt: ParseStatement2> <e:@R>
=> build_while_block(Meta::new(s,e),cond,stmt),
<s:@L> "return" <value: ParseExpression> ";"<e:@R>
=> build_return(Meta::new(s,e),value),
<subs: ParseSubstitution> ";"
=> subs,
<s:@L> <lhe: ParseExpression> "===" <rhe: ParseExpression> ";" <e:@R>
=> build_constraint_equality(Meta::new(s,e),lhe,rhe),
<s:@L> "log" "(" <arg: ParseExpression> ")" ";" <e:@R>
=> build_log_call(Meta::new(s,e),arg),
<s:@L> "assert" "(" <arg: ParseExpression> ")" ";" <e:@R>
=> build_assert(Meta::new(s,e),arg),
ParseBlock
};
ParseStatement3 : Statement = {
<dec: ParseDeclaration> ";"
=> dec,
ParseStatement
};
// ====================================================================
// Variable
// ====================================================================
ParseVarAccess : Access = {
<arr_dec: ParseArrayAcc> => build_array_access(arr_dec),
<component_acc: ParseComponentAcc> => build_component_access(component_acc),
};
ParseArrayAcc: Expression = {
"["<dim: ParseExpression>"]" => dim
};
ParseComponentAcc: String = {
"." <id: IDENTIFIER> => id,
};
ParseVariable : (String,Vec<Access>) = {
<name:IDENTIFIER> <access: ParseVarAccess*>
=> (name,access),
};
// ====================================================================
// Expression
// ====================================================================
Listable: Vec<Expression> = {
<e:(<ParseExpression> ",")*> <tail: ParseExpression>
=> {
let mut e = e;
e.push(tail);
e
},
};
InfixOpTier<Op,NextTier> : Expression = {
<s:@L> <lhe:InfixOpTier<Op,NextTier>> <infix_op:Op> <rhe:NextTier> <e: @R>
=> build_infix(Meta::new(s,e),lhe,infix_op,rhe),
NextTier
};
PrefixOpTier<Op,NextTier >: Expression = {
<s:@L> <prefix_op:Op> <rhe:NextTier> <e:@R>
=> build_prefix(Meta::new(s,e),prefix_op,rhe),
NextTier
};
pub ParseExpression: Expression = {
Expression13,
Expression12,
};
// ops: e ? a : i
Expression13 : Expression = {
<s:@L> <cond: Expression12> "?" <if_true: Expression12> ":" <if_false: Expression12> <e:@R>
=> build_inline_switch_op(Meta::new(s,e),cond,if_true,if_false),
};
// ops: ||
Expression12 = InfixOpTier<ParseBoolOr,Expression11>;
// ops: &&
Expression11 = InfixOpTier<ParseBoolAnd,Expression10>;
// ops: == != < > <= >=
Expression10 = InfixOpTier<ParseCmpOpCodes,Expression9>;
// ops: |
Expression9 = InfixOpTier<ParseBitOr,Expression8>;
// ops: ^
Expression8 = InfixOpTier<ParseBitXOR,Expression7>;
// ops: &
Expression7 = InfixOpTier<ParseBitAnd,Expression6>;
// ops: << >>
Expression6 = InfixOpTier<ParseShift,Expression5>;
// ops: + -
Expression5 = InfixOpTier<ParseAddAndSub,Expression4>;
// ops: * / \\ %
Expression4 = InfixOpTier<ParseMulDiv,Expression3>;
// ops: **
Expression3 = InfixOpTier<ParseExp,Expression2>;
// ops: Unary - ! ~
Expression2 = PrefixOpTier<ParseExpressionPrefixOpcode,Expression1>;
// function call, array inline
Expression1: Expression = {
<s:@L> <id: IDENTIFIER> "(" <args: Listable?> ")" <e:@R>
=> match args {
None => build_call(Meta::new(s,e),id,Vec::new()),
Some(a) => build_call(Meta::new(s,e),id,a),
},
<s:@L> "[" <values: Listable> "]" <e:@R>
=> build_array_in_line(Meta::new(s,e),values),
Expression0,
};
// Literal, parentheses
Expression0: Expression = {
<s:@L> <variable: ParseVariable> <e:@L>
=> {
let (name,access) = variable;
build_variable(Meta::new(s,e),name,access)
},
<s:@L> <value:DECNUMBER> <e:@L>
=> build_number(Meta::new(s,e),value),
<s:@L> <value:HEXNUMBER> <e:@L>
=> build_number(Meta::new(s,e),value),
"(" <Expression12> ")"
};
// ====================================================================
// Terminals
// ====================================================================
ParseExpressionPrefixOpcode: ExpressionPrefixOpcode = {
"!" => ExpressionPrefixOpcode::BoolNot,
"~" => ExpressionPrefixOpcode::Complement,
"-" => ExpressionPrefixOpcode::Sub,
};
ParseBoolOr : ExpressionInfixOpcode = {
"||" => ExpressionInfixOpcode::BoolOr,
};
ParseBoolAnd : ExpressionInfixOpcode = {
"&&" => ExpressionInfixOpcode::BoolAnd,
};
ParseCmpOpCodes : ExpressionInfixOpcode = {
"==" => ExpressionInfixOpcode::Eq,
"!=" => ExpressionInfixOpcode::NotEq,
"<" => ExpressionInfixOpcode::Lesser,
">" => ExpressionInfixOpcode::Greater,
"<=" => ExpressionInfixOpcode::LesserEq,
">=" => ExpressionInfixOpcode::GreaterEq,
};
ParseBitOr : ExpressionInfixOpcode = {
"|" => ExpressionInfixOpcode::BitOr,
};
ParseBitAnd : ExpressionInfixOpcode = {
"&" => ExpressionInfixOpcode::BitAnd,
};
ParseShift : ExpressionInfixOpcode = {
"<<" => ExpressionInfixOpcode::ShiftL,
">>" => ExpressionInfixOpcode::ShiftR,
};
ParseAddAndSub : ExpressionInfixOpcode = {
"+" => ExpressionInfixOpcode::Add,
"-" => ExpressionInfixOpcode::Sub,
};
ParseMulDiv : ExpressionInfixOpcode = {
"*" => ExpressionInfixOpcode::Mul,
"/" => ExpressionInfixOpcode::Div,
"\\" => ExpressionInfixOpcode::IntDiv,
"%" => ExpressionInfixOpcode::Mod,
};
ParseExp : ExpressionInfixOpcode = {
"**" => ExpressionInfixOpcode::Pow,
};
ParseBitXOR : ExpressionInfixOpcode = {
"^" => ExpressionInfixOpcode::BitXor,
};
ParseAssignOp: AssignOp = {
"=" => AssignOp::AssignVar,
"<--" => AssignOp::AssignSignal,
"<==" => AssignOp::AssignConstraintSignal,
};
DECNUMBER: BigInt = {
r"[0-9]+" => BigInt::parse_bytes(&<>.as_bytes(),10).expect("failed to parse base10")
};
HEXNUMBER : BigInt = {
r"0x[0-9A-Fa-f]*" => BigInt::parse_bytes(&(<>.as_bytes()[2..]),16).expect("failed to parse base16")
};
IDENTIFIER : String = {
r"[$_]*[a-zA-Z][a-zA-Z$_0-9]*" => String::from(<>)
};
STRING : String = {
<s:r#""[^"]*""#> => String::from(&s[1..s.len()-1])
};
SMALL_DECNUMBER: usize = {
r"[0-9]+" => usize::from_str(<>).expect("failed to parse number")
};
// Version used by pragma to describe the compiler, its syntax is Number1.Number2.Number3...
Version : Version = {
<version: SMALL_DECNUMBER> "." <subversion:SMALL_DECNUMBER> "." <subsubversion:SMALL_DECNUMBER> => {
(version, subversion, subsubversion)
}
};

105
parser/src/lib.rs Normal file
View File

@@ -0,0 +1,105 @@
extern crate num_bigint_dig as num_bigint;
extern crate num_traits;
extern crate serde;
extern crate serde_derive;
#[macro_use]
extern crate lalrpop_util;
lalrpop_mod!(pub lang);
mod errors;
mod include_logic;
mod parser_logic;
use include_logic::FileStack;
use program_structure::error_definition::{Report, ReportCollection};
use program_structure::file_definition::{FileLibrary};
use program_structure::program_archive::ProgramArchive;
use std::path::PathBuf;
use std::str::FromStr;
pub type Version = (usize, usize, usize);
pub fn run_parser(file: String, version: &str) -> Result<(ProgramArchive, ReportCollection), (FileLibrary, ReportCollection)> {
let mut file_library = FileLibrary::new();
let mut definitions = Vec::new();
let mut main_components = Vec::new();
let mut file_stack = FileStack::new(PathBuf::from(file));
let mut warnings = Vec::new();
while let Some(crr_file) = FileStack::take_next(&mut file_stack) {
let (path, src) = open_file(crr_file).map_err(|e| (file_library.clone(), vec![e]))?;
let file_id = file_library.add_file(path.clone(), src.clone());
let program =
parser_logic::parse_file(&src, file_id).map_err(|e| (file_library.clone(), vec![e]))?;
if let Some(main) = program.main_component {
main_components.push((file_id, main));
}
let includes = program.includes;
definitions.push((file_id, program.definitions));
for include in includes {
FileStack::add_include(&mut file_stack, include)
.map_err(|e| (file_library.clone(), vec![e]))?;
}
warnings.append(&mut check_number_version(path, program.compiler_version, parse_number_version(version)).map_err(|e| (file_library.clone(), vec![e]))?);
}
if main_components.len() == 0 {
let report = errors::NoMainError::produce_report();
Err((file_library, vec![report]))
} else if main_components.len() > 1 {
let report = errors::MultipleMainError::produce_report();
Err((file_library, vec![report]))
}
else{
let (main_id, main_component) = main_components.pop().unwrap();
let result_program_archive =
ProgramArchive::new(file_library, main_id, main_component, definitions);
match result_program_archive {
Err((lib, rep)) => {
Err((lib, rep))
}
Ok(program_archive) => {
Ok((program_archive, warnings))
}
}
}
}
fn open_file(path: PathBuf) -> Result<(String, String), Report> /* path, src*/ {
use errors::FileOsError;
use std::fs::read_to_string;
let path_str = format!("{:?}", path);
read_to_string(path)
.map(|contents| (path_str.clone(), contents))
.map_err(|_| FileOsError { path: path_str.clone() })
.map_err(|e| FileOsError::produce_report(e))
}
fn parse_number_version(version: &str) -> Version{
let version_splitted: Vec<&str> = version.split(".").collect();
(usize::from_str(version_splitted[0]).unwrap(), usize::from_str(version_splitted[1]).unwrap(), usize::from_str(version_splitted[2]).unwrap())
}
fn check_number_version(file_path: String, version_file: Option<Version>, version_compiler: Version) -> Result<ReportCollection, Report>{
use errors::{CompilerVersionError, NoCompilerVersionWarning};
if let Some(required_version) = version_file {
if required_version.0 == version_compiler.0
&& required_version.1 == version_compiler.1
&& required_version.2 <= version_compiler.2{
Ok(vec![])
}
else{
let report = CompilerVersionError::produce_report(CompilerVersionError{path: file_path, required_version: required_version, version: version_compiler});
Err(report)
}
}
else{
let report = NoCompilerVersionWarning::produce_report(NoCompilerVersionWarning{path: file_path, version: version_compiler});
Ok(vec![report])
}
}

100
parser/src/parser_logic.rs Normal file
View File

@@ -0,0 +1,100 @@
use super::errors::{ParsingError, UnclosedCommentError};
use super::lang;
use program_structure::ast::AST;
use program_structure::error_definition::Report;
use program_structure::file_definition::FileID;
pub fn preprocess(expr: &str, file_id: FileID) -> Result<String, Report> {
let mut pp = String::new();
let mut state = 0;
let mut loc = 0;
let mut block_start = 0;
let mut it = expr.chars();
while let Some(c0) = it.next() {
loc += 1;
match (state, c0) {
(0, '/') => {
loc += 1;
match it.next() {
Some('/') => {
state = 1;
pp.push(' ');
pp.push(' ');
}
Some('*') => {
block_start = loc;
state = 2;
pp.push(' ');
pp.push(' ');
}
Some(c1) => {
pp.push(c0);
pp.push(c1);
}
None => {
pp.push(c0);
break;
}
}
}
(0, _) => pp.push(c0),
(1, '\n') => {
pp.push(c0);
state = 0;
}
(2, '*') => {
loc += 1;
match it.next() {
Some('/') => {
pp.push(' ');
pp.push(' ');
state = 0;
}
Some(c) => {
pp.push(' ');
for _i in 0..c.len_utf8() {
pp.push(' ');
}
}
None => {
let error =
UnclosedCommentError { location: block_start..block_start, file_id };
return Err(UnclosedCommentError::produce_report(error));
}
}
}
(_, c) => {
for _i in 0..c.len_utf8() {
pp.push(' ');
}
}
}
}
Ok(pp)
}
pub fn parse_file(src: &str, file_id: FileID) -> Result<AST, Report> {
use lalrpop_util::ParseError::*;
lang::ParseAstParser::new()
.parse(&preprocess(src, file_id)?)
.map_err(|parse_error| match parse_error {
InvalidToken { location } => ParsingError {
file_id,
msg: format!("{:?}", parse_error),
location: location..location,
},
UnrecognizedToken { ref token, .. } => ParsingError {
file_id,
msg: format!("{:?}", parse_error),
location: token.0..token.2,
},
ExtraToken { ref token } => ParsingError {
file_id,
msg: format!("{:?}", parse_error),
location: token.0..token.2,
},
_ => ParsingError { file_id, msg: format!("{:?}", parse_error), location: 0..0 },
})
.map_err(|parsing_error| ParsingError::produce_report(parsing_error))
}

View File

@@ -0,0 +1,10 @@
[package]
name = "program_analysis"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
log = "0.4"
num-bigint-dig = "0.6"
program_structure = { path = "../program_structure" }

View File

@@ -0,0 +1,34 @@
use program_structure::error_code::ReportCode;
use program_structure::error_definition::Report;
use program_structure::file_definition::{FileID, FileLocation};
pub struct ShadowedVariableWarning {
pub name: String,
pub primary_file_id: FileID,
pub primary_location: FileLocation,
pub secondary_file_id: FileID,
pub secondary_location: FileLocation,
}
impl ShadowedVariableWarning {
pub fn produce_report(error: Self) -> Report {
let mut report = Report::warning(
format!("Declaration of variable '{}' shadows previous declaration", error.name),
ReportCode::ShadowedVariable,
);
report.add_primary(
error.primary_location,
error.primary_file_id,
"shadowing declaration here".to_string()
);
report.add_secondary(
error.secondary_location,
error.secondary_file_id,
Some("shadowed variable is declared here".to_string())
);
report.add_note(
format!("Consider renaming the second occurence of '{}'", error.name)
);
report
}
}

View File

@@ -0,0 +1,4 @@
pub mod utils;
pub mod errors;
pub mod visitor;
pub mod shadowing_analysis;

View File

@@ -0,0 +1,88 @@
use log::debug;
use anyhow::{anyhow, Result};
use std::collections::HashMap;
use super::visitor::Visitor;
use super::errors::ShadowedVariableWarning;
use program_structure::program_archive::ProgramArchive;
use program_structure::error_definition::ReportCollection;
use program_structure::ast::{Expression, Meta, VariableType, Statement};
#[derive(Default, Clone)]
struct Scope {
parent: Option<Box<Scope>>,
variables: HashMap<String, Meta>,
}
impl Scope {
fn push(&self) -> Scope {
Scope {
parent: Some(Box::new(self.clone())),
variables: self.variables.clone()
}
}
fn pop(&self) -> Result<Scope> {
if let Some(parent) = &self.parent {
Ok(*parent.clone())
} else {
Err(anyhow!("cannot pop outermost scope"))
}
}
fn add(&mut self, var: &str, meta: &Meta) -> Option<&Meta> {
if self.variables.contains_key(var) {
self.variables.get(var)
} else {
self.variables.insert(var.to_string(), meta.clone());
None
}
}
}
#[derive(Default)]
pub struct ShadowingAnalysis {
scope: Scope,
reports: ReportCollection
}
impl ShadowingAnalysis {
pub fn new() -> ShadowingAnalysis {
ShadowingAnalysis::default()
}
pub fn run(&mut self, program: &ProgramArchive) -> ReportCollection {
self.visit_templates(&program.templates);
self.visit_functions(&program.functions);
self.reports.clone()
}
}
impl Visitor for ShadowingAnalysis {
fn visit_block(&mut self, _meta: &Meta, stmts: &[Statement]) {
self.scope = self.scope.push();
for stmt in stmts {
self.visit_stmt(stmt);
}
self.scope
.pop()
.expect("not in outermost scope");
}
fn visit_expr(&mut self, _: &Expression) {
// Override visit_expr to ignore expressions.
}
fn visit_declaration(&mut self, primary_meta: &Meta, _xtype: &VariableType, name: &str, _dimensions: &[Expression], _is_constant: &bool) {
if let Some(secondary_meta) = self.scope.add(name, primary_meta) {
debug!("declaration of {name} shadows previous declaration");
self.reports.push(ShadowedVariableWarning::produce_report(ShadowedVariableWarning {
name: name.to_string(),
primary_file_id: primary_meta.get_file_id(),
primary_location: primary_meta.file_location(),
secondary_file_id: secondary_meta.get_file_id(),
secondary_location: secondary_meta.file_location(),
}));
}
}
}

View File

@@ -0,0 +1,86 @@
use program_structure::ast::{AssignOp, Expression, ExpressionInfixOpcode, ExpressionPrefixOpcode, Statement};
pub trait ToString {
fn to_string(&self) -> String;
}
impl ToString for Statement {
fn to_string(&self) -> String {
match self {
Statement::IfThenElse { .. } => "if-then-else statement".to_string(),
Statement::While { .. } => "while statement".to_string(),
Statement::Return { .. } => "return statement".to_string(),
Statement::InitializationBlock { .. } => "initialization block".to_string(),
Statement::Declaration { name, .. } => format!("declaration of '{name}'"),
Statement::Substitution { var, op, .. } => format!("{} '{var}'", &op.to_string()),
Statement::ConstraintEquality { .. } => "constraint equality".to_string(),
Statement::LogCall { .. } => "log call".to_string(),
Statement::Block { .. } => "basic block".to_string(),
Statement::Assert { .. } => "assert statement".to_string(),
}
}
}
impl ToString for Expression {
fn to_string(&self) -> String {
match self {
Expression::InfixOp { infix_op, .. } => format!("{} expression", infix_op.to_string()),
Expression::PrefixOp { prefix_op, .. } => format!("{} expression", prefix_op.to_string()),
Expression::InlineSwitchOp { .. } => "inline switch expression".to_string(),
Expression::Variable { name, .. } => format!("variable expression '{name}'"),
Expression::Number ( _, value ) => format!("constant expression '{value}'"),
Expression::Call { id, .. } => format!("call to '{id}'"),
Expression::ArrayInLine { .. } => "inline array expression ".to_string(),
}
}
}
impl ToString for ExpressionInfixOpcode {
fn to_string(&self) -> String {
match self {
ExpressionInfixOpcode::Mul => "mutliplication",
ExpressionInfixOpcode::Div => "division",
ExpressionInfixOpcode::Add => "addition",
ExpressionInfixOpcode::Sub => "subtraction",
ExpressionInfixOpcode::Pow => "power",
ExpressionInfixOpcode::IntDiv => "integer division",
ExpressionInfixOpcode::Mod => "modulo",
ExpressionInfixOpcode::ShiftL => "left-shift",
ExpressionInfixOpcode::ShiftR => "right-shift",
ExpressionInfixOpcode::LesserEq => "less than or equal",
ExpressionInfixOpcode::GreaterEq => "greater than or equal",
ExpressionInfixOpcode::Lesser => "less than",
ExpressionInfixOpcode::Greater => "greater than",
ExpressionInfixOpcode::Eq => "equal",
ExpressionInfixOpcode::NotEq => "not equal",
ExpressionInfixOpcode::BoolOr => "boolean OR",
ExpressionInfixOpcode::BoolAnd => "boolean AND",
ExpressionInfixOpcode::BitOr => "bitwise OR",
ExpressionInfixOpcode::BitAnd => "bitwise AND",
ExpressionInfixOpcode::BitXor => "bitwise XOR",
}
.to_string()
}
}
impl ToString for ExpressionPrefixOpcode {
fn to_string(&self) -> String {
match self {
ExpressionPrefixOpcode::Sub => "additive inverse",
ExpressionPrefixOpcode::BoolNot => "boolean NOT",
ExpressionPrefixOpcode::Complement => "bitwise complement",
}
.to_string()
}
}
impl ToString for AssignOp {
fn to_string(&self) -> String {
match self {
AssignOp::AssignVar => "assignment to variable",
AssignOp::AssignSignal => "assignment to signal",
AssignOp::AssignConstraintSignal => "constraint assignment to signal"
}
.to_string()
}
}

View File

@@ -0,0 +1,197 @@
use log::debug;
use num_bigint_dig::BigInt;
use program_structure::program_library::function_data::{FunctionInfo, FunctionData};
use program_structure::program_library::template_data::{TemplateInfo, TemplateData};
use program_structure::ast::{Access, AssignOp, Expression, ExpressionInfixOpcode, ExpressionPrefixOpcode, Meta, Statement, VariableType};
use super::utils::ToString;
pub trait Visitor {
fn visit_templates(&mut self, templates: &TemplateInfo) {
for (name, template) in templates.iter() {
self.visit_template(name, template);
}
}
fn visit_template(&mut self, name: &str, template: &TemplateData) {
debug!("visiting template '{name}'");
self.visit_stmt(template.get_body());
}
fn visit_functions(&mut self, functions: &FunctionInfo) {
for (name, function) in functions.iter() {
self.visit_function(name, function);
}
}
fn visit_function(&mut self, name: &str, function: &FunctionData) {
debug!("visiting function '{name}'");
self.visit_stmt(function.get_body());
}
fn visit_stmt(&mut self, stmt: &Statement) {
debug!("visiting {}", stmt.to_string());
match stmt {
Statement::IfThenElse { meta, cond, if_case, else_case } =>
self.visit_ite(meta, cond, if_case, else_case),
Statement::While { meta, cond, stmt } =>
self.visit_while(meta, cond, stmt),
Statement::Return { meta, value } =>
self.visit_return(meta, value),
Statement::InitializationBlock { meta, xtype, initializations } =>
self.visit_init_block(meta, xtype, initializations),
Statement::Declaration { meta, xtype, name, dimensions, is_constant } =>
self.visit_declaration(meta, xtype, name, dimensions, is_constant),
Statement::Substitution { meta, var, access, op, rhe } =>
self.visit_substitution(meta, var, access, op, rhe),
Statement::ConstraintEquality { meta, lhe, rhe } =>
self.visit_constraint_eq(meta, lhe, rhe),
Statement::LogCall { meta, arg } =>
self.visit_log_call(meta, arg),
Statement::Block { meta, stmts } =>
self.visit_block(meta, stmts),
Statement::Assert { meta, arg } =>
self.visit_assert(meta, arg),
}
}
fn visit_expr(&mut self, expr: &Expression) {
debug!("visiting {}", expr.to_string());
match expr {
Expression::InfixOp { meta, lhe, infix_op, rhe } =>
self.visit_infix_op(meta, lhe, infix_op, rhe),
Expression::PrefixOp { meta, prefix_op, rhe } =>
self.visit_prefix_op(meta, prefix_op, rhe),
Expression::InlineSwitchOp { meta, cond, if_true, if_false } =>
self.visit_inline_switch_op(meta, cond, if_true, if_false),
Expression::Variable { meta, name, access } =>
self.visit_variable(meta, name, access),
Expression::Number(meta, value) =>
self.visit_number(meta, value),
Expression::Call { meta, id, args } =>
self.visit_call(meta, id, args),
Expression::ArrayInLine { meta, values } =>
self.visit_array_inline(meta, values),
}
}
// Statement visitors.
fn visit_ite(&mut self, _meta: &Meta, cond: &Expression, if_case: &Statement, else_case: &Option<Box<Statement>>) {
// Default implementation does nothing.
self.visit_expr(cond);
self.visit_stmt(if_case);
if let Some(else_case) = else_case {
self.visit_stmt(else_case);
}
}
fn visit_while(&mut self, _meta: &Meta, cond: &Expression, stmt: &Statement) {
// Default implementation does nothing.
self.visit_expr(cond);
self.visit_stmt(stmt);
}
fn visit_return(&mut self, _meta: &Meta, value: &Expression) {
// Default implementation does nothing.
self.visit_expr(value);
}
fn visit_init_block(&mut self, _meta: &Meta, _xtype: &VariableType, initializations: &[Statement]) {
// Default implementation does nothing.
for init in initializations {
self.visit_stmt(init);
}
}
fn visit_declaration(&mut self, _meta: &Meta, _xtype: &VariableType, _name: &str, dimensions: &[Expression], _is_constant: &bool) {
// Default implementation does nothing.
for dim in dimensions {
self.visit_expr(dim);
}
}
fn visit_substitution(&mut self, _meta: &Meta, _var: &str, _access: &[Access], _op: &AssignOp, rhe: &Expression) {
// Default implementation does nothing.
self.visit_expr(rhe);
}
fn visit_constraint_eq(&mut self, _meta: &Meta, lhe: &Expression, rhe: &Expression) {
// Default implementation does nothing.
self.visit_expr(lhe);
self.visit_expr(rhe);
}
fn visit_log_call(&mut self, _meta: &Meta, arg: &Expression) {
// Default implementation does nothing.
self.visit_expr(arg);
}
fn visit_block(&mut self, _meta: &Meta, stmts: &[Statement]) {
// Default implementation does nothing.
for stmt in stmts {
self.visit_stmt(stmt);
}
}
fn visit_assert(&mut self, _meta: &Meta, arg: &Expression) {
// Default implementation does nothing.
self.visit_expr(arg);
}
// Expression visitors.
fn visit_infix_op(&mut self, _meta: &Meta, lhe: &Expression, _op: &ExpressionInfixOpcode, rhe: &Expression) {
// Default implementation does nothing.
self.visit_expr(lhe);
self.visit_expr(rhe);
}
fn visit_prefix_op(&mut self, _meta: &Meta, _op: &ExpressionPrefixOpcode, rhe: &Expression) {
// Default implementation does nothing.
self.visit_expr(rhe);
}
fn visit_inline_switch_op(&mut self, _meta: &Meta, cond: &Expression, if_true: &Expression, if_false: &Expression) {
// Default implementation does nothing.
self.visit_expr(cond);
self.visit_expr(if_true);
self.visit_expr(if_false);
}
fn visit_variable(&mut self, _meta: &Meta, _name: &str, _access: &[Access]) {
// Default implementation does nothing.
}
fn visit_number(&mut self, _meta: &Meta, _value: &BigInt) {
// Default implementation does nothing.
}
fn visit_call(&mut self, _meta: &Meta, _id: &str, args: &[Expression]) {
// Default implementation does nothing.
for arg in args {
self.visit_expr(arg);
}
}
fn visit_array_inline(&mut self, _meta: &Meta, values: &[Expression]) {
// Default implementation does nothing.
for value in values {
self.visit_expr(value);
}
}
}

View File

@@ -0,0 +1,17 @@
[package]
name = "program_structure"
version = "2.0.1"
authors = ["hermeGarcia <hermegarcianavarro@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
codespan = "0.9.0"
codespan-reporting = "0.9.0"
regex = "1.1.2"
rustc-hex = "2.0.1"
num-bigint-dig = "0.6.0"
num-traits = "0.2.6"
serde = "1.0.82"
serde_derive = "1.0.91"

View File

@@ -0,0 +1,11 @@
use super::ast::AssignOp;
impl AssignOp {
pub fn is_signal_operator(self) -> bool {
use AssignOp::*;
match self {
AssignConstraintSignal | AssignSignal => true,
_ => false,
}
}
}

View File

@@ -0,0 +1,383 @@
use crate::file_definition::FileLocation;
use num_bigint::BigInt;
use serde_derive::{Deserialize, Serialize};
pub trait FillMeta {
fn fill(&mut self, file_id: usize, elem_id: &mut usize);
}
pub type MainComponent = (Vec<String>, Expression);
pub fn build_main_component(public: Vec<String>, call: Expression) -> MainComponent {
(public, call)
}
pub type Version = (usize, usize, usize);
#[derive(Clone)]
pub struct Meta {
pub elem_id: usize,
pub start: usize,
pub end: usize,
pub location: FileLocation,
pub file_id: Option<usize>,
pub component_inference: Option<String>,
type_knowledge: TypeKnowledge,
memory_knowledge: MemoryKnowledge,
}
impl Meta {
pub fn new(start: usize, end: usize) -> Meta {
Meta {
end,
start,
elem_id: 0,
location: start..end,
file_id: Option::None,
component_inference: None,
type_knowledge: TypeKnowledge::default(),
memory_knowledge: MemoryKnowledge::default(),
}
}
pub fn change_location(&mut self, location: FileLocation, file_id: Option<usize>) {
self.location = location;
self.file_id = file_id;
}
pub fn get_start(&self) -> usize {
self.location.start
}
pub fn get_end(&self) -> usize {
self.location.end
}
pub fn get_file_id(&self) -> usize {
if let Option::Some(id) = self.file_id {
id
} else {
panic!("Empty file id accessed")
}
}
pub fn get_memory_knowledge(&self) -> &MemoryKnowledge {
&self.memory_knowledge
}
pub fn get_type_knowledge(&self) -> &TypeKnowledge {
&self.type_knowledge
}
pub fn get_mut_memory_knowledge(&mut self) -> &mut MemoryKnowledge {
&mut self.memory_knowledge
}
pub fn get_mut_type_knowledge(&mut self) -> &mut TypeKnowledge {
&mut self.type_knowledge
}
pub fn file_location(&self) -> FileLocation {
self.location.clone()
}
pub fn set_file_id(&mut self, file_id: usize) {
self.file_id = Option::Some(file_id);
}
}
#[derive(Clone)]
pub struct AST {
pub meta: Meta,
pub compiler_version: Option<Version>,
pub includes: Vec<String>,
pub definitions: Vec<Definition>,
pub main_component: Option<MainComponent>,
}
impl AST {
pub fn new(
meta: Meta,
compiler_version: Option<Version>,
includes: Vec<String>,
definitions: Vec<Definition>,
main_component: Option<MainComponent>,
) -> AST {
AST { meta, compiler_version, includes, definitions, main_component }
}
}
#[derive(Clone)]
pub enum Definition {
Template {
meta: Meta,
name: String,
args: Vec<String>,
arg_location: FileLocation,
body: Statement,
parallel: bool,
},
Function {
meta: Meta,
name: String,
args: Vec<String>,
arg_location: FileLocation,
body: Statement,
},
}
pub fn build_template(
meta: Meta,
name: String,
args: Vec<String>,
arg_location: FileLocation,
body: Statement,
parallel: bool,
) -> Definition {
Definition::Template {
meta,
name,
args,
arg_location,
body,
parallel,
}
}
pub fn build_function(
meta: Meta,
name: String,
args: Vec<String>,
arg_location: FileLocation,
body: Statement,
) -> Definition {
Definition::Function { meta, name, args, arg_location, body }
}
#[derive(Clone)]
pub enum Statement {
IfThenElse {
meta: Meta,
cond: Expression,
if_case: Box<Statement>,
else_case: Option<Box<Statement>>,
},
While {
meta: Meta,
cond: Expression,
stmt: Box<Statement>,
},
Return {
meta: Meta,
value: Expression,
},
InitializationBlock {
meta: Meta,
xtype: VariableType,
initializations: Vec<Statement>,
},
Declaration {
meta: Meta,
xtype: VariableType,
name: String,
dimensions: Vec<Expression>,
is_constant: bool,
},
Substitution {
meta: Meta,
var: String,
access: Vec<Access>,
op: AssignOp,
rhe: Expression,
},
ConstraintEquality {
meta: Meta,
lhe: Expression,
rhe: Expression,
},
LogCall {
meta: Meta,
arg: Expression,
},
Block {
meta: Meta,
stmts: Vec<Statement>,
},
Assert {
meta: Meta,
arg: Expression,
},
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum SignalElementType {
Empty,
Binary,
FieldElement,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum SignalType {
Output,
Input,
Intermediate,
}
#[derive(Copy, Clone, PartialEq, Ord, PartialOrd, Eq)]
pub enum VariableType {
Var,
Signal(SignalType, SignalElementType),
Component,
}
#[derive(Clone)]
pub enum Expression {
InfixOp {
meta: Meta,
lhe: Box<Expression>,
infix_op: ExpressionInfixOpcode,
rhe: Box<Expression>,
},
PrefixOp {
meta: Meta,
prefix_op: ExpressionPrefixOpcode,
rhe: Box<Expression>,
},
InlineSwitchOp {
meta: Meta,
cond: Box<Expression>,
if_true: Box<Expression>,
if_false: Box<Expression>,
},
Variable {
meta: Meta,
name: String,
access: Vec<Access>,
},
Number(Meta, BigInt),
Call {
meta: Meta,
id: String,
args: Vec<Expression>,
},
ArrayInLine {
meta: Meta,
values: Vec<Expression>,
},
}
#[derive(Clone)]
pub enum Access {
ComponentAccess(String),
ArrayAccess(Expression),
}
pub fn build_component_access(acc: String) -> Access {
Access::ComponentAccess(acc)
}
pub fn build_array_access(expr: Expression) -> Access {
Access::ArrayAccess(expr)
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum AssignOp {
AssignVar,
AssignSignal,
AssignConstraintSignal,
}
#[derive(Copy, Clone, PartialEq)]
pub enum ExpressionInfixOpcode {
Mul,
Div,
Add,
Sub,
Pow,
IntDiv,
Mod,
ShiftL,
ShiftR,
LesserEq,
GreaterEq,
Lesser,
Greater,
Eq,
NotEq,
BoolOr,
BoolAnd,
BitOr,
BitAnd,
BitXor,
}
#[derive(Copy, Clone, PartialEq)]
pub enum ExpressionPrefixOpcode {
Sub,
BoolNot,
Complement,
}
// Knowledge buckets
#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
pub enum TypeReduction {
Variable,
Component,
Signal,
}
#[derive(Default, Clone)]
pub struct TypeKnowledge {
reduces_to: Option<TypeReduction>,
}
impl TypeKnowledge {
pub fn new() -> TypeKnowledge {
TypeKnowledge::default()
}
pub fn set_reduces_to(&mut self, reduces_to: TypeReduction) {
self.reduces_to = Option::Some(reduces_to);
}
pub fn get_reduces_to(&self) -> TypeReduction {
if let Option::Some(t) = &self.reduces_to {
*t
} else {
panic!("reduces_to knowledge is been look at without being initialized");
}
}
pub fn is_var(&self) -> bool {
self.get_reduces_to() == TypeReduction::Variable
}
pub fn is_component(&self) -> bool {
self.get_reduces_to() == TypeReduction::Component
}
pub fn is_signal(&self) -> bool {
self.get_reduces_to() == TypeReduction::Signal
}
}
#[derive(Default, Clone)]
pub struct MemoryKnowledge {
concrete_dimensions: Option<Vec<usize>>,
full_length: Option<usize>,
abstract_memory_address: Option<usize>,
}
impl MemoryKnowledge {
pub fn new() -> MemoryKnowledge {
MemoryKnowledge::default()
}
pub fn set_concrete_dimensions(&mut self, value: Vec<usize>) {
self.full_length = Option::Some(value.iter().fold(1, |p, v| p * (*v)));
self.concrete_dimensions = Option::Some(value);
}
pub fn set_abstract_memory_address(&mut self, value: usize) {
self.abstract_memory_address = Option::Some(value);
}
pub fn get_concrete_dimensions(&self) -> &[usize] {
if let Option::Some(v) = &self.concrete_dimensions {
v
} else {
panic!("concrete dimensions was look at without being initialized");
}
}
pub fn get_full_length(&self) -> usize {
if let Option::Some(v) = &self.full_length {
*v
} else {
panic!("full dimension was look at without being initialized");
}
}
pub fn get_abstract_memory_address(&self) -> usize {
if let Option::Some(v) = &self.abstract_memory_address {
*v
} else {
panic!("abstract memory address was look at without being initialized");
}
}
}

View File

@@ -0,0 +1,18 @@
use super::ast::*;
impl AST {
pub fn get_includes(&self) -> &Vec<String> {
&self.includes
}
pub fn get_version(&self) -> &Option<Version> {
&self.compiler_version
}
pub fn get_definitions(&self) -> &Vec<Definition> {
&self.definitions
}
pub fn decompose(self) -> (Meta, Option<Version>, Vec<String>, Vec<Definition>, Option<MainComponent>) {
(self.meta, self.compiler_version, self.includes, self.definitions, self.main_component)
}
}

View File

@@ -0,0 +1,71 @@
use super::ast::*;
use super::expression_builders::*;
use super::statement_builders::*;
use crate::ast::{Access, Expression, VariableType};
use num_bigint::BigInt;
#[derive(Clone)]
pub struct Symbol {
pub name: String,
pub is_array: Vec<Expression>,
pub init: Option<Expression>,
}
pub fn assign_with_op_shortcut(
op: ExpressionInfixOpcode,
meta: Meta,
variable: (String, Vec<Access>),
rhe: Expression,
) -> Statement {
let (var, access) = variable;
let variable = build_variable(meta.clone(), var.clone(), access.clone());
let infix = build_infix(meta.clone(), variable, op, rhe);
build_substitution(meta, var, access, AssignOp::AssignVar, infix)
}
pub fn plusplus(meta: Meta, variable: (String, Vec<Access>)) -> Statement {
let one = build_number(meta.clone(), BigInt::from(1));
assign_with_op_shortcut(ExpressionInfixOpcode::Add, meta, variable, one)
}
pub fn subsub(meta: Meta, variable: (String, Vec<Access>)) -> Statement {
let one = build_number(meta.clone(), BigInt::from(1));
assign_with_op_shortcut(ExpressionInfixOpcode::Sub, meta, variable, one)
}
pub fn for_into_while(
meta: Meta,
init: Statement,
cond: Expression,
step: Statement,
body: Statement,
) -> Statement {
let while_body = build_block(body.get_meta().clone(), vec![body, step]);
let while_statement = build_while_block(meta.clone(), cond, while_body);
build_block(meta, vec![init, while_statement])
}
pub fn split_declaration_into_single_nodes(
meta: Meta,
xtype: VariableType,
symbols: Vec<Symbol>,
) -> Statement {
let mut initializations = Vec::new();
for symbol in symbols {
let with_meta = meta.clone();
let has_type = xtype;
let name = symbol.name.clone();
let dimensions = symbol.is_array;
let possible_init = symbol.init;
let single_declaration = build_declaration(with_meta, has_type, name, dimensions);
initializations.push(single_declaration);
if let Option::Some(init) = possible_init {
let substitution =
build_substitution(meta.clone(), symbol.name, vec![], AssignOp::AssignVar, init);
initializations.push(substitution);
}
}
build_initialization_block(meta, xtype, initializations)
}

View File

@@ -0,0 +1,46 @@
use super::ast::*;
use num_bigint::BigInt;
use Expression::*;
pub fn build_infix(
meta: Meta,
lhe: Expression,
infix_op: ExpressionInfixOpcode,
rhe: Expression,
) -> Expression {
InfixOp { meta, infix_op, lhe: Box::new(lhe), rhe: Box::new(rhe) }
}
pub fn build_prefix(meta: Meta, prefix_op: ExpressionPrefixOpcode, rhe: Expression) -> Expression {
PrefixOp { meta, prefix_op, rhe: Box::new(rhe) }
}
pub fn build_inline_switch_op(
meta: Meta,
cond: Expression,
if_true: Expression,
if_false: Expression,
) -> Expression {
InlineSwitchOp {
meta,
cond: Box::new(cond),
if_true: Box::new(if_true),
if_false: Box::new(if_false),
}
}
pub fn build_variable(meta: Meta, name: String, access: Vec<Access>) -> Expression {
Variable { meta, name, access }
}
pub fn build_number(meta: Meta, value: BigInt) -> Expression {
Expression::Number(meta, value)
}
pub fn build_call(meta: Meta, id: String, args: Vec<Expression>) -> Expression {
Call { meta, id, args }
}
pub fn build_array_in_line(meta: Meta, values: Vec<Expression>) -> Expression {
ArrayInLine { meta, values }
}

View File

@@ -0,0 +1,175 @@
use super::ast::*;
impl Expression {
pub fn get_meta(&self) -> &Meta {
use Expression::*;
match self {
InfixOp { meta, .. }
| PrefixOp { meta, .. }
| InlineSwitchOp { meta, .. }
| Variable { meta, .. }
| Number(meta, ..)
| Call { meta, .. }
| ArrayInLine { meta, .. } => meta,
}
}
pub fn get_mut_meta(&mut self) -> &mut Meta {
use Expression::*;
match self {
InfixOp { meta, .. }
| PrefixOp { meta, .. }
| InlineSwitchOp { meta, .. }
| Variable { meta, .. }
| Number(meta, ..)
| Call { meta, .. }
| ArrayInLine { meta, .. } => meta,
}
}
pub fn is_array(&self) -> bool {
use Expression::*;
if let ArrayInLine { .. } = self {
true
} else {
false
}
}
pub fn is_infix(&self) -> bool {
use Expression::*;
if let InfixOp { .. } = self {
true
} else {
false
}
}
pub fn is_prefix(&self) -> bool {
use Expression::*;
if let PrefixOp { .. } = self {
true
} else {
false
}
}
pub fn is_switch(&self) -> bool {
use Expression::*;
if let InlineSwitchOp { .. } = self {
true
} else {
false
}
}
pub fn is_variable(&self) -> bool {
use Expression::*;
if let Variable { .. } = self {
true
} else {
false
}
}
pub fn is_number(&self) -> bool {
use Expression::*;
if let Number(..) = self {
true
} else {
false
}
}
pub fn is_call(&self) -> bool {
use Expression::*;
if let Call { .. } = self {
true
} else {
false
}
}
}
impl FillMeta for Expression {
fn fill(&mut self, file_id: usize, element_id: &mut usize) {
use Expression::*;
self.get_mut_meta().elem_id = *element_id;
*element_id += 1;
match self {
Number(meta, _) => fill_number(meta, file_id, element_id),
Variable { meta, access, .. } => fill_variable(meta, access, file_id, element_id),
InfixOp { meta, lhe, rhe, .. } => fill_infix(meta, lhe, rhe, file_id, element_id),
PrefixOp { meta, rhe, .. } => fill_prefix(meta, rhe, file_id, element_id),
InlineSwitchOp { meta, cond, if_false, if_true, .. } => {
fill_inline_switch_op(meta, cond, if_true, if_false, file_id, element_id)
}
Call { meta, args, .. } => fill_call(meta, args, file_id, element_id),
ArrayInLine { meta, values, .. } => {
fill_array_inline(meta, values, file_id, element_id)
}
}
}
}
fn fill_number(meta: &mut Meta, file_id: usize, _element_id: &mut usize) {
meta.set_file_id(file_id);
}
fn fill_variable(meta: &mut Meta, access: &mut [Access], file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
for acc in access {
if let Access::ArrayAccess(e) = acc {
e.fill(file_id, element_id)
}
}
}
fn fill_infix(
meta: &mut Meta,
lhe: &mut Expression,
rhe: &mut Expression,
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
lhe.fill(file_id, element_id);
rhe.fill(file_id, element_id);
}
fn fill_prefix(meta: &mut Meta, rhe: &mut Expression, file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
rhe.fill(file_id, element_id);
}
fn fill_inline_switch_op(
meta: &mut Meta,
cond: &mut Expression,
if_true: &mut Expression,
if_false: &mut Expression,
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
cond.fill(file_id, element_id);
if_true.fill(file_id, element_id);
if_false.fill(file_id, element_id);
}
fn fill_call(meta: &mut Meta, args: &mut [Expression], file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
for a in args {
a.fill(file_id, element_id);
}
}
fn fill_array_inline(
meta: &mut Meta,
values: &mut [Expression],
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
for v in values {
v.fill(file_id, element_id);
}
}

View File

@@ -0,0 +1,8 @@
mod assign_op_impl;
pub mod ast;
mod ast_impl;
pub mod ast_shortcuts;
pub mod expression_builders;
mod expression_impl;
pub mod statement_builders;
mod statement_impl;

View File

@@ -0,0 +1,62 @@
use super::ast::*;
use Statement::*;
pub fn build_conditional_block(
meta: Meta,
cond: Expression,
if_case: Statement,
else_case: Option<Statement>,
) -> Statement {
IfThenElse { meta, cond, else_case: else_case.map(|s| Box::new(s)), if_case: Box::new(if_case) }
}
pub fn build_while_block(meta: Meta, cond: Expression, stmt: Statement) -> Statement {
While { meta, cond, stmt: Box::new(stmt) }
}
pub fn build_initialization_block(
meta: Meta,
xtype: VariableType,
initializations: Vec<Statement>,
) -> Statement {
InitializationBlock { meta, xtype, initializations }
}
pub fn build_block(meta: Meta, stmts: Vec<Statement>) -> Statement {
Block { meta, stmts }
}
pub fn build_return(meta: Meta, value: Expression) -> Statement {
Return { meta, value }
}
pub fn build_declaration(
meta: Meta,
xtype: VariableType,
name: String,
dimensions: Vec<Expression>,
) -> Statement {
let is_constant = true;
Declaration { meta, xtype, name, dimensions, is_constant }
}
pub fn build_substitution(
meta: Meta,
var: String,
access: Vec<Access>,
op: AssignOp,
rhe: Expression,
) -> Statement {
Substitution { meta, var, access, op, rhe }
}
pub fn build_constraint_equality(meta: Meta, lhe: Expression, rhe: Expression) -> Statement {
ConstraintEquality { meta, lhe, rhe }
}
pub fn build_log_call(meta: Meta, arg: Expression) -> Statement {
LogCall { meta, arg }
}
pub fn build_assert(meta: Meta, arg: Expression) -> Statement {
Assert { meta, arg }
}

View File

@@ -0,0 +1,247 @@
use super::ast::*;
impl Statement {
pub fn get_meta(&self) -> &Meta {
use Statement::*;
match self {
IfThenElse { meta, .. }
| While { meta, .. }
| Return { meta, .. }
| Declaration { meta, .. }
| Substitution { meta, .. }
| LogCall { meta, .. }
| Block { meta, .. }
| Assert { meta, .. }
| ConstraintEquality { meta, .. }
| InitializationBlock { meta, .. } => meta,
}
}
pub fn get_mut_meta(&mut self) -> &mut Meta {
use Statement::*;
match self {
IfThenElse { meta, .. }
| While { meta, .. }
| Return { meta, .. }
| Declaration { meta, .. }
| Substitution { meta, .. }
| LogCall { meta, .. }
| Block { meta, .. }
| Assert { meta, .. }
| ConstraintEquality { meta, .. }
| InitializationBlock { meta, .. } => meta,
}
}
pub fn is_if_then_else(&self) -> bool {
use Statement::IfThenElse;
if let IfThenElse { .. } = self {
true
} else {
false
}
}
pub fn is_while(&self) -> bool {
use Statement::While;
if let While { .. } = self {
true
} else {
false
}
}
pub fn is_return(&self) -> bool {
use Statement::Return;
if let Return { .. } = self {
true
} else {
false
}
}
pub fn is_initialization_block(&self) -> bool {
use Statement::InitializationBlock;
if let InitializationBlock { .. } = self {
true
} else {
false
}
}
pub fn is_declaration(&self) -> bool {
use Statement::Declaration;
if let Declaration { .. } = self {
true
} else {
false
}
}
pub fn is_substitution(&self) -> bool {
use Statement::Substitution;
if let Substitution { .. } = self {
true
} else {
false
}
}
pub fn is_constraint_equality(&self) -> bool {
use Statement::ConstraintEquality;
if let ConstraintEquality { .. } = self {
true
} else {
false
}
}
pub fn is_log_call(&self) -> bool {
use Statement::LogCall;
if let LogCall { .. } = self {
true
} else {
false
}
}
pub fn is_block(&self) -> bool {
use Statement::Block;
if let Block { .. } = self {
true
} else {
false
}
}
pub fn is_assert(&self) -> bool {
use Statement::Assert;
if let Assert { .. } = self {
true
} else {
false
}
}
}
impl FillMeta for Statement {
fn fill(&mut self, file_id: usize, element_id: &mut usize) {
use Statement::*;
self.get_mut_meta().elem_id = *element_id;
*element_id += 1;
match self {
IfThenElse { meta, cond, if_case, else_case, .. } => {
fill_conditional(meta, cond, if_case, else_case, file_id, element_id)
}
While { meta, cond, stmt } => fill_while(meta, cond, stmt, file_id, element_id),
Return { meta, value } => fill_return(meta, value, file_id, element_id),
InitializationBlock { meta, initializations, .. } => {
fill_initialization(meta, initializations, file_id, element_id)
}
Declaration { meta, dimensions, .. } => {
fill_declaration(meta, dimensions, file_id, element_id)
}
Substitution { meta, access, rhe, .. } => {
fill_substitution(meta, access, rhe, file_id, element_id)
}
ConstraintEquality { meta, lhe, rhe } => {
fill_constraint_equality(meta, lhe, rhe, file_id, element_id)
}
LogCall { meta, arg, .. } => fill_log_call(meta, arg, file_id, element_id),
Block { meta, stmts, .. } => fill_block(meta, stmts, file_id, element_id),
Assert { meta, arg, .. } => fill_assert(meta, arg, file_id, element_id),
}
}
}
fn fill_conditional(
meta: &mut Meta,
cond: &mut Expression,
if_case: &mut Statement,
else_case: &mut Option<Box<Statement>>,
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
cond.fill(file_id, element_id);
if_case.fill(file_id, element_id);
if let Option::Some(s) = else_case {
s.fill(file_id, element_id);
}
}
fn fill_while(
meta: &mut Meta,
cond: &mut Expression,
stmt: &mut Statement,
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
cond.fill(file_id, element_id);
stmt.fill(file_id, element_id);
}
fn fill_return(meta: &mut Meta, value: &mut Expression, file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
value.fill(file_id, element_id);
}
fn fill_initialization(
meta: &mut Meta,
initializations: &mut [Statement],
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
for init in initializations {
init.fill(file_id, element_id);
}
}
fn fill_declaration(
meta: &mut Meta,
dimensions: &mut [Expression],
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
for d in dimensions {
d.fill(file_id, element_id);
}
}
fn fill_substitution(
meta: &mut Meta,
access: &mut [Access],
rhe: &mut Expression,
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
rhe.fill(file_id, element_id);
for a in access {
if let Access::ArrayAccess(e) = a {
e.fill(file_id, element_id);
}
}
}
fn fill_constraint_equality(
meta: &mut Meta,
lhe: &mut Expression,
rhe: &mut Expression,
file_id: usize,
element_id: &mut usize,
) {
meta.set_file_id(file_id);
lhe.fill(file_id, element_id);
rhe.fill(file_id, element_id);
}
fn fill_log_call(meta: &mut Meta, arg: &mut Expression, file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
arg.fill(file_id, element_id);
}
fn fill_block(meta: &mut Meta, stmts: &mut [Statement], file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
for s in stmts {
s.fill(file_id, element_id);
}
}
fn fill_assert(meta: &mut Meta, arg: &mut Expression, file_id: usize, element_id: &mut usize) {
meta.set_file_id(file_id);
arg.fill(file_id, element_id);
}

View File

@@ -0,0 +1,11 @@
extern crate num_bigint_dig as num_bigint;
extern crate num_traits;
pub mod abstract_syntax_tree;
pub mod program_library;
pub mod utils;
// Library interface
pub use abstract_syntax_tree::*;
pub use program_library::*;
pub use utils::*;

View File

@@ -0,0 +1,148 @@
use core::fmt;
use std::fmt::Formatter;
#[derive(Copy, Clone)]
pub enum ReportCode {
AssertWrongType,
ParseFail,
CompilerVersionError,
WrongTypesInAssignOperation,
WrongNumberOfArguments(usize, usize),
UndefinedFunction,
UndefinedTemplate,
UninitializedSymbolInExpression,
UnableToTypeFunction,
UnreachableConstraints,
UnknownIndex,
UnknownDimension,
SameFunctionDeclaredTwice,
SameTemplateDeclaredTwice,
SameSymbolDeclaredTwice,
StaticInfoWasOverwritten,
SignalInLineInitialization,
SignalOutsideOriginalScope,
FunctionWrongNumberOfArguments,
FunctionInconsistentTyping,
FunctionPathWithoutReturn,
FunctionReturnError,
ForbiddenDeclarationInFunction,
NonHomogeneousArray,
NonBooleanCondition,
NonCompatibleBranchTypes,
NonEqualTypesInExpression,
NonExistentSymbol,
NoMainFoundInProject,
NoCompilerVersionWarning,
MultipleMainInComponent,
TemplateCallAsArgument,
TemplateWrongNumberOfArguments,
TemplateWithReturnStatement,
TypeCantBeUseAsCondition,
EmptyArrayInlineDeclaration,
PrefixOperatorWithWrongTypes,
InfixOperatorWithWrongTypes,
InvalidArgumentInCall,
InconsistentReturnTypesInBlock,
InconsistentStaticInformation,
InvalidArrayAccess,
InvalidSignalAccess,
InvalidArraySize,
InvalidArrayType,
ForStatementIllConstructed,
BadArrayAccess,
AssigningAComponentTwice,
AssigningASignalTwice,
NotAllowedOperation,
ConstraintGeneratorInFunction,
WrongSignalTags,
InvalidPartialArray,
MustBeSingleArithmetic,
ExpectedDimDiffGotDim(usize, usize),
RuntimeError,
UnknownTemplate,
NonQuadratic,
NonConstantArrayLength,
NonComputableExpression,
// Constraint analysis codes
UnconstrainedSignal,
OneConstraintIntermediate,
NoOutputInInstance,
ErrorWat2Wasm,
// Circom Spectacle specific codes
ShadowedVariable,
}
impl fmt::Display for ReportCode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use self::ReportCode::*;
let string_format = match self {
ParseFail => "P1000",
NoMainFoundInProject => "P1001",
MultipleMainInComponent => "P1002",
CompilerVersionError => "P1003",
NoCompilerVersionWarning => "P1004",
WrongTypesInAssignOperation => "T2000",
UndefinedFunction => "T2001",
UndefinedTemplate => "T2002",
UninitializedSymbolInExpression => "T2003",
UnableToTypeFunction => "T2004",
UnreachableConstraints => "T2005",
SameFunctionDeclaredTwice => "T2006",
SameTemplateDeclaredTwice => "T2007",
SameSymbolDeclaredTwice => "T2008",
StaticInfoWasOverwritten => "T2009",
SignalInLineInitialization => "T2010",
SignalOutsideOriginalScope => "T2011",
FunctionWrongNumberOfArguments => "T2012",
FunctionInconsistentTyping => "T2013",
FunctionPathWithoutReturn => "T2014",
FunctionReturnError => "T2015",
ForbiddenDeclarationInFunction => "T2016",
NonHomogeneousArray => "T2017",
NonBooleanCondition => "T2018",
NonCompatibleBranchTypes => "T2019",
NonEqualTypesInExpression => "T2020",
NonExistentSymbol => "T2021",
TemplateCallAsArgument => "T2022",
TemplateWrongNumberOfArguments => "T2023",
TemplateWithReturnStatement => "T2024",
TypeCantBeUseAsCondition => "T2025",
EmptyArrayInlineDeclaration => "T2026",
PrefixOperatorWithWrongTypes => "T2027",
InfixOperatorWithWrongTypes => "T2028",
InvalidArgumentInCall => "T2029",
InconsistentReturnTypesInBlock => "T2030",
InconsistentStaticInformation => "T2031",
InvalidArrayAccess => "T2032",
InvalidSignalAccess => "T2046",
InvalidArraySize => "T2033",
InvalidArrayType => "T2034",
ForStatementIllConstructed => "T2035",
BadArrayAccess => "T2035",
AssigningAComponentTwice => "T2036",
AssigningASignalTwice => "T2037",
NotAllowedOperation => "T2038",
ConstraintGeneratorInFunction => "T2039",
WrongSignalTags => "T2040",
AssertWrongType => "T2041",
UnknownIndex => "T2042",
InvalidPartialArray => "T2043",
MustBeSingleArithmetic => "T2044",
ExpectedDimDiffGotDim(..) => "T2045",
RuntimeError => "T3001",
UnknownDimension => "T20460",
UnknownTemplate => "T20461",
NonQuadratic => "T20462",
NonConstantArrayLength => "T20463",
NonComputableExpression => "T20464",
WrongNumberOfArguments(..) => "T20465",
// Constraint analysis codes
UnconstrainedSignal => "CA01",
OneConstraintIntermediate => "CA02",
NoOutputInInstance => "CA03",
ErrorWat2Wasm => "W01",
// Circom Spectacle specific codes
ShadowedVariable => "CS0001",
};
f.write_str(string_format)
}
}

View File

@@ -0,0 +1,143 @@
use super::error_code::ReportCode;
use super::file_definition::{FileID, FileLibrary, FileLocation};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term;
pub type ReportCollection = Vec<Report>;
pub type DiagnosticCode = String;
type ReportLabel = Label<FileID>;
type ReportNote = String;
#[derive(Copy, Clone)]
enum MessageCategory {
Error,
Warning,
Info,
}
#[derive(Clone)]
pub struct Report {
category: MessageCategory,
message: String,
primary: Vec<ReportLabel>,
secondary: Vec<ReportLabel>,
notes: Vec<ReportNote>,
}
impl Report {
fn new(category: MessageCategory, message: String, _code: ReportCode) -> Report {
Report {
category,
message,
primary: Vec::new(),
secondary: Vec::new(),
notes: Vec::new(),
}
}
pub fn print_reports(reports: &[Report], file_library: &FileLibrary) {
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
let writer = StandardStream::stderr(ColorChoice::Always);
let mut config = term::Config::default();
let mut diagnostics = Vec::new();
let files = file_library.to_storage();
for report in reports.iter() {
diagnostics.push(report.to_diagnostic());
}
config.styles.header_warning.set_intense(false);
for diagnostic in diagnostics.iter() {
let print_result = term::emit(&mut writer.lock(), &config, files, &diagnostic);
if print_result.is_err() {
panic!("Error printing reports")
}
}
}
pub fn error(message: String, code: ReportCode) -> Report {
Report::new(MessageCategory::Error, message, code)
}
pub fn warning(message: String, code: ReportCode) -> Report {
Report::new(MessageCategory::Warning, message, code)
}
pub fn info(message: String, code: ReportCode) -> Report {
Report::new(MessageCategory::Info, message, code)
}
pub fn add_primary(
&mut self,
location: FileLocation,
file_id: FileID,
message: String,
) -> &mut Self {
let label = ReportLabel::primary(file_id, location).with_message(message);
self.get_mut_primary().push(label);
self
}
pub fn add_secondary(
&mut self,
location: FileLocation,
file_id: FileID,
possible_message: Option<String>,
) -> &mut Self {
let mut label = ReportLabel::secondary(file_id, location);
if let Option::Some(message) = possible_message {
label = label.with_message(message);
}
self.get_mut_secondary().push(label);
self
}
pub fn add_note(&mut self, note: String) -> &mut Self {
self.get_mut_notes().push(note);
self
}
fn to_diagnostic(&self) -> Diagnostic<FileID> {
let mut labels = self.get_primary().clone();
let mut secondary = self.get_secondary().clone();
labels.append(&mut secondary);
match self.get_category() {
MessageCategory::Error => Diagnostic::error(),
MessageCategory::Warning => Diagnostic::warning(),
MessageCategory::Info => Diagnostic::note(),
}
.with_message(self.get_message())
.with_labels(labels)
.with_notes(self.get_notes().clone())
}
fn get_category(&self) -> &MessageCategory {
&self.category
}
fn get_message(&self) -> &String {
&self.message
}
fn get_primary(&self) -> &Vec<ReportLabel> {
&self.primary
}
fn get_mut_primary(&mut self) -> &mut Vec<ReportLabel> {
&mut self.primary
}
fn get_secondary(&self) -> &Vec<ReportLabel> {
&self.secondary
}
fn get_mut_secondary(&mut self) -> &mut Vec<ReportLabel> {
&mut self.secondary
}
fn get_notes(&self) -> &Vec<ReportNote> {
&self.notes
}
fn get_mut_notes(&mut self) -> &mut Vec<ReportNote> {
&mut self.notes
}
}

View File

@@ -0,0 +1,46 @@
use codespan_reporting::files::{Files, SimpleFiles};
use std::ops::Range;
pub type FileSource = String;
pub type FilePath = String;
pub type FileID = usize;
pub type FileLocation = Range<usize>;
type FileStorage = SimpleFiles<FilePath, FileSource>;
#[derive(Clone)]
pub struct FileLibrary {
files: FileStorage,
}
impl Default for FileLibrary {
fn default() -> Self {
FileLibrary { files: FileStorage::new() }
}
}
impl FileLibrary {
pub fn new() -> FileLibrary {
FileLibrary::default()
}
pub fn add_file(&mut self, file_name: FilePath, file_source: FileSource) -> FileID {
self.get_mut_files().add(file_name, file_source)
}
pub fn get_line(&self, start: usize, file_id: FileID) -> Option<usize> {
match self.files.line_index(file_id, start) {
Some(lines) => Some(lines + 1),
None => None,
}
}
pub fn to_storage(&self) -> &FileStorage {
&self.get_files()
}
fn get_files(&self) -> &FileStorage {
&self.files
}
fn get_mut_files(&mut self) -> &mut FileStorage {
&mut self.files
}
}
pub fn generate_file_location(start: usize, end: usize) -> FileLocation {
start..end
}

View File

@@ -0,0 +1,67 @@
use super::ast::{FillMeta, Statement};
use super::file_definition::FileID;
use crate::file_definition::FileLocation;
use std::collections::HashMap;
pub type FunctionInfo = HashMap<String, FunctionData>;
#[derive(Clone)]
pub struct FunctionData {
name: String,
file_id: FileID,
num_of_params: usize,
name_of_params: Vec<String>,
param_location: FileLocation,
body: Statement,
}
impl FunctionData {
pub fn new(
name: String,
file_id: FileID,
mut body: Statement,
num_of_params: usize,
name_of_params: Vec<String>,
param_location: FileLocation,
elem_id: &mut usize,
) -> FunctionData {
body.fill(file_id, elem_id);
FunctionData { name, file_id, body, name_of_params, param_location, num_of_params }
}
pub fn get_file_id(&self) -> FileID {
self.file_id
}
pub fn get_body(&self) -> &Statement {
&self.body
}
pub fn get_body_as_vec(&self) -> &Vec<Statement> {
match &self.body {
Statement::Block { stmts, .. } => stmts,
_ => panic!("Function body should be a block"),
}
}
pub fn get_mut_body(&mut self) -> &mut Statement {
&mut self.body
}
pub fn replace_body(&mut self, new: Statement) -> Statement {
std::mem::replace(&mut self.body, new)
}
pub fn get_mut_body_as_vec(&mut self) -> &mut Vec<Statement> {
match &mut self.body {
Statement::Block { stmts, .. } => stmts,
_ => panic!("Function body should be a block"),
}
}
pub fn get_param_location(&self) -> FileLocation {
self.param_location.clone()
}
pub fn get_num_of_params(&self) -> usize {
self.num_of_params
}
pub fn get_name_of_params(&self) -> &Vec<String> {
&self.name_of_params
}
pub fn get_name(&self) -> &str {
&self.name
}
}

View File

@@ -0,0 +1,8 @@
use super::ast;
pub mod error_code;
pub mod error_definition;
pub mod file_definition;
pub mod function_data;
pub mod program_archive;
pub mod program_merger;
pub mod template_data;

View File

@@ -0,0 +1,134 @@
use super::ast::{Definition, Expression, MainComponent};
use super::file_definition::{FileID, FileLibrary};
use super::function_data::{FunctionData, FunctionInfo};
use super::program_merger::Merger;
use super::template_data::{TemplateData, TemplateInfo};
use crate::abstract_syntax_tree::ast::FillMeta;
use std::collections::HashSet;
use crate::error_definition::Report;
type Contents = Vec<(FileID, Vec<Definition>)>;
#[derive(Clone)]
pub struct ProgramArchive {
pub id_max: usize,
pub file_id_main: FileID,
pub file_library: FileLibrary,
pub functions: FunctionInfo,
pub templates: TemplateInfo,
pub function_keys: HashSet<String>,
pub template_keys: HashSet<String>,
pub public_inputs: Vec<String>,
pub initial_template_call: Expression,
}
impl ProgramArchive {
pub fn new(
file_library: FileLibrary,
file_id_main: FileID,
main_component: MainComponent,
program_contents: Contents,
) -> Result<ProgramArchive, (FileLibrary, Vec<Report>)> {
let mut merger = Merger::new();
let mut reports = vec![];
for (file_id, definitions) in program_contents {
if let Err(mut errs) = merger.add_definitions(file_id, definitions) {
reports.append(&mut errs);
}
}
let (mut fresh_id, functions, templates) = merger.decompose();
let mut function_keys = HashSet::new();
let mut template_keys = HashSet::new();
for key in functions.keys() {
function_keys.insert(key.clone());
}
for key in templates.keys() {
template_keys.insert(key.clone());
}
let (public_inputs, mut initial_template_call) = main_component;
initial_template_call.fill(file_id_main, &mut fresh_id);
if reports.is_empty() {
Ok(ProgramArchive {
id_max: fresh_id,
file_id_main,
file_library,
functions,
templates,
public_inputs,
initial_template_call,
function_keys,
template_keys,
})
} else {
Err((file_library, reports))
}
}
//file_id_main
pub fn get_file_id_main(&self) -> &FileID {
&self.file_id_main
}
//template functions
pub fn contains_template(&self, template_name: &str) -> bool {
self.templates.contains_key(template_name)
}
pub fn get_template_data(&self, template_name: &str) -> &TemplateData {
assert!(self.contains_template(template_name));
self.templates.get(template_name).unwrap()
}
pub fn get_mut_template_data(&mut self, template_name: &str) -> &mut TemplateData {
assert!(self.contains_template(template_name));
self.templates.get_mut(template_name).unwrap()
}
pub fn get_template_names(&self) -> &HashSet<String> {
&self.template_keys
}
pub fn get_templates(&self) -> &TemplateInfo {
&self.templates
}
pub fn get_mut_templates(&mut self) -> &mut TemplateInfo {
&mut self.templates
}
pub fn remove_template(&mut self, id: &str) {
self.template_keys.remove(id);
self.templates.remove(id);
}
//functions functions
pub fn contains_function(&self, function_name: &str) -> bool {
self.get_functions().contains_key(function_name)
}
pub fn get_function_data(&self, function_name: &str) -> &FunctionData {
assert!(self.contains_function(function_name));
self.get_functions().get(function_name).unwrap()
}
pub fn get_mut_function_data(&mut self, function_name: &str) -> &mut FunctionData {
assert!(self.contains_function(function_name));
self.functions.get_mut(function_name).unwrap()
}
pub fn get_function_names(&self) -> &HashSet<String> {
&self.function_keys
}
pub fn get_functions(&self) -> &FunctionInfo {
&self.functions
}
pub fn get_mut_functions(&mut self) -> &mut FunctionInfo {
&mut self.functions
}
pub fn remove_function(&mut self, id: &str) {
self.function_keys.remove(id);
self.functions.remove(id);
}
//main_component functions
pub fn get_public_inputs_main_component(&self) -> &Vec<String> {
&self.public_inputs
}
pub fn get_main_expression(&self) -> &Expression {
&self.initial_template_call
}
// FileLibrary functions
pub fn get_file_library(&self) -> &FileLibrary {
&self.file_library
}
}

View File

@@ -0,0 +1,107 @@
use super::ast::Definition;
use super::error_code::ReportCode;
use super::error_definition::Report;
use super::file_definition::FileID;
use super::function_data::{FunctionData, FunctionInfo};
use super::template_data::{TemplateData, TemplateInfo};
pub struct Merger {
fresh_id: usize,
function_info: FunctionInfo,
template_info: TemplateInfo,
}
impl Default for Merger {
fn default() -> Self {
Merger {
fresh_id: 0,
function_info: FunctionInfo::new(),
template_info: TemplateInfo::new(),
}
}
}
impl Merger {
pub fn new() -> Merger {
Merger::default()
}
pub fn add_definitions(&mut self, file_id: FileID, definitions: Vec<Definition>) -> Result<(), Vec<Report>> {
let mut reports = vec![];
for definition in definitions {
let (name, meta) = match definition {
Definition::Template { name, args, arg_location, body, meta, parallel } => {
if self.contains_function(&name) || self.contains_template(&name) {
(Option::Some(name), meta)
} else {
let new_data = TemplateData::new(
name.clone(),
file_id,
body,
args.len(),
args,
arg_location,
&mut self.fresh_id,
parallel,
);
self.get_mut_template_info().insert(name.clone(), new_data);
(Option::None, meta)
}
}
Definition::Function { name, body, args, arg_location, meta } => {
if self.contains_function(&name) || self.contains_template(&name) {
(Option::Some(name), meta)
} else {
let new_data = FunctionData::new(
name.clone(),
file_id,
body,
args.len(),
args,
arg_location,
&mut self.fresh_id,
);
self.get_mut_function_info().insert(name.clone(), new_data);
(Option::None, meta)
}
}
};
if let Option::Some(definition_name) = name {
let mut report = Report::error(
String::from("Duplicated callable symbol"),
ReportCode::SameSymbolDeclaredTwice,
);
report.add_primary(
meta.file_location(),
file_id,
format!("{} is already in use", definition_name),
);
reports.push(report);
}
}
if reports.is_empty() { Ok(()) } else { Err(reports) }
}
pub fn contains_function(&self, function_name: &str) -> bool {
self.get_function_info().contains_key(function_name)
}
fn get_function_info(&self) -> &FunctionInfo {
&self.function_info
}
fn get_mut_function_info(&mut self) -> &mut FunctionInfo {
&mut self.function_info
}
pub fn contains_template(&self, template_name: &str) -> bool {
self.get_template_info().contains_key(template_name)
}
fn get_template_info(&self) -> &TemplateInfo {
&self.template_info
}
fn get_mut_template_info(&mut self) -> &mut TemplateInfo {
&mut self.template_info
}
pub fn decompose(self) -> (usize, FunctionInfo, TemplateInfo) {
(self.fresh_id, self.function_info, self.template_info)
}
}

View File

@@ -0,0 +1,142 @@
use super::ast;
use super::ast::{FillMeta, SignalElementType, Statement};
use super::file_definition::FileID;
use crate::file_definition::FileLocation;
use std::collections::hash_map::HashMap;
pub type TemplateInfo = HashMap<String, TemplateData>;
type SignalInfo = HashMap<String, (usize, SignalElementType)>;
#[derive(Clone)]
pub struct TemplateData {
file_id: FileID,
name: String,
body: Statement,
num_of_params: usize,
name_of_params: Vec<String>,
param_location: FileLocation,
input_signals: SignalInfo,
output_signals: SignalInfo,
is_parallel: bool,
}
impl TemplateData {
pub fn new(
name: String,
file_id: FileID,
mut body: Statement,
num_of_params: usize,
name_of_params: Vec<String>,
param_location: FileLocation,
elem_id: &mut usize,
is_parallel: bool,
) -> TemplateData {
body.fill(file_id, elem_id);
let mut input_signals = SignalInfo::new();
let mut output_signals = SignalInfo::new();
fill_inputs_and_outputs(&body, &mut input_signals, &mut output_signals);
TemplateData {
name,
file_id,
body,
num_of_params,
name_of_params,
param_location,
input_signals,
output_signals,
is_parallel,
}
}
pub fn get_file_id(&self) -> FileID {
self.file_id
}
pub fn get_body(&self) -> &Statement {
&self.body
}
pub fn get_body_as_vec(&self) -> &Vec<Statement> {
match &self.body {
Statement::Block { stmts, .. } => stmts,
_ => panic!("Function body should be a block"),
}
}
pub fn get_mut_body(&mut self) -> &mut Statement {
&mut self.body
}
pub fn get_mut_body_as_vec(&mut self) -> &mut Vec<Statement> {
match &mut self.body {
Statement::Block { stmts, .. } => stmts,
_ => panic!("Function body should be a block"),
}
}
pub fn get_num_of_params(&self) -> usize {
self.num_of_params
}
pub fn get_param_location(&self) -> FileLocation {
self.param_location.clone()
}
pub fn get_name_of_params(&self) -> &Vec<String> {
&self.name_of_params
}
pub fn get_input_info(&self, name: &str) -> Option<&(usize, SignalElementType)> {
self.input_signals.get(name)
}
pub fn get_output_info(&self, name: &str) -> Option<&(usize, SignalElementType)> {
self.output_signals.get(name)
}
pub fn get_inputs(&self) -> &SignalInfo {
&self.input_signals
}
pub fn get_outputs(&self) -> &SignalInfo {
&self.output_signals
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn is_parallel(&self) -> bool {
self.is_parallel
}
}
fn fill_inputs_and_outputs(
template_statement: &Statement,
input_signals: &mut SignalInfo,
output_signals: &mut SignalInfo,
) {
match template_statement {
Statement::IfThenElse { if_case, else_case, .. } => {
fill_inputs_and_outputs(if_case, input_signals, output_signals);
if let Option::Some(else_value) = else_case {
fill_inputs_and_outputs(else_value, input_signals, output_signals);
}
}
Statement::Block { stmts, .. } => {
for stmt in stmts.iter() {
fill_inputs_and_outputs(stmt, input_signals, output_signals);
}
}
Statement::While { stmt, .. } => {
fill_inputs_and_outputs(stmt, input_signals, output_signals);
}
Statement::InitializationBlock { initializations, .. } => {
for initialization in initializations.iter() {
fill_inputs_and_outputs(initialization, input_signals, output_signals);
}
}
Statement::Declaration { xtype, name, dimensions, .. } => {
if let ast::VariableType::Signal(stype, tag) = xtype {
let signal_name = name.clone();
let dim = dimensions.len();
match stype {
ast::SignalType::Input => {
input_signals.insert(signal_name, (dim, *tag));
}
ast::SignalType::Output => {
output_signals.insert(signal_name, (dim, *tag));
}
_ => {} //no need to deal with intermediate signals
}
}
}
_ => {}
}
}

View File

@@ -0,0 +1,27 @@
use num_bigint::BigInt;
const P_STR: &str = "21888242871839275222246405745257275088548364400416034343698204186575808495617";
pub struct UsefulConstants {
p: BigInt,
}
impl Clone for UsefulConstants {
fn clone(&self) -> Self {
UsefulConstants { p: self.p.clone() }
}
}
impl Default for UsefulConstants {
fn default() -> Self {
UsefulConstants { p: BigInt::parse_bytes(P_STR.as_bytes(), 10).expect("can not parse p") }
}
}
impl UsefulConstants {
pub fn new() -> UsefulConstants {
UsefulConstants::default()
}
pub fn get_p(&self) -> &BigInt {
&self.p
}
}

View File

@@ -0,0 +1,468 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::marker::PhantomData;
pub trait VarInfo {}
pub trait SignalInfo {}
pub trait ComponentInfo {}
#[derive(Clone)]
pub struct OnlyVars;
impl VarInfo for OnlyVars {}
#[derive(Clone)]
pub struct OnlySignals;
impl SignalInfo for OnlySignals {}
#[derive(Clone)]
pub struct OnlyComponents;
impl ComponentInfo for OnlyComponents {}
#[derive(Clone)]
pub struct FullEnvironment;
impl VarInfo for FullEnvironment {}
impl SignalInfo for FullEnvironment {}
impl ComponentInfo for FullEnvironment {}
pub type VarEnvironment<VC> = RawEnvironment<OnlyVars, (), (), VC>;
pub type SignalEnvironment<SC> = RawEnvironment<OnlySignals, (), SC, ()>;
pub type ComponentEnvironment<CC> = RawEnvironment<OnlyComponents, CC, (), ()>;
pub type CircomEnvironment<CC, SC, VC> = RawEnvironment<FullEnvironment, CC, SC, VC>;
pub enum CircomEnvironmentError {
NonExistentSymbol,
}
#[derive(Clone)]
pub struct RawEnvironment<T, CC, SC, VC> {
components: HashMap<String, CC>,
inputs: HashMap<String, SC>,
outputs: HashMap<String, SC>,
intermediates: HashMap<String, SC>,
variables: Vec<VariableBlock<VC>>,
behaviour: PhantomData<T>,
}
impl<T, CC, SC, VC> Default for RawEnvironment<T, CC, SC, VC> {
fn default() -> Self {
let variables = vec![VariableBlock::new()];
RawEnvironment {
components: HashMap::new(),
inputs: HashMap::new(),
outputs: HashMap::new(),
intermediates: HashMap::new(),
variables,
behaviour: PhantomData,
}
}
}
impl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>
where
T: VarInfo + SignalInfo + ComponentInfo,
{
pub fn has_symbol(&self, symbol: &str) -> bool {
self.has_signal(symbol) || self.has_component(symbol) || self.has_variable(symbol)
}
}
impl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC> {
pub fn merge(
left: RawEnvironment<T, CC, SC, VC>,
right: RawEnvironment<T, CC, SC, VC>,
using: fn(VC, VC) -> VC,
) -> RawEnvironment<T, CC, SC, VC> {
let mut components = left.components;
let mut inputs = left.inputs;
let mut outputs = left.outputs;
let mut intermediates = left.intermediates;
components.extend(right.components);
inputs.extend(right.inputs);
outputs.extend(right.outputs);
intermediates.extend(right.intermediates);
let mut variables_left = left.variables;
let mut variables_right = right.variables;
let mut variables = Vec::new();
while !variables_left.is_empty() && !variables_right.is_empty() {
let left_block = variables_left.pop().unwrap();
let right_block = variables_right.pop().unwrap();
let merged_blocks = VariableBlock::merge(left_block, right_block, using);
variables.push(merged_blocks);
}
variables.reverse();
RawEnvironment {
components,
inputs,
intermediates,
outputs,
variables,
behaviour: PhantomData,
}
}
}
impl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>
where
T: VarInfo,
{
fn block_with_variable_symbol(&self, symbol: &str) -> Option<&VariableBlock<VC>> {
let variables = &self.variables;
let mut act = variables.len();
while act > 0 {
if VariableBlock::contains_variable(&variables[act - 1], symbol) {
return Option::Some(&variables[act - 1]);
}
act -= 1;
}
Option::None
}
fn mut_block_with_variable_symbol(&mut self, symbol: &str) -> Option<&mut VariableBlock<VC>> {
let variables = &mut self.variables;
let mut act = variables.len();
while act > 0 {
if VariableBlock::contains_variable(&variables[act - 1], symbol) {
return Option::Some(&mut variables[act - 1]);
}
act -= 1;
}
Option::None
}
pub fn new() -> RawEnvironment<T, CC, SC, VC> {
RawEnvironment::default()
}
pub fn add_variable_block(&mut self) {
self.variables.push(VariableBlock::new());
}
pub fn remove_variable_block(&mut self) {
assert!(!self.variables.is_empty());
self.variables.pop();
}
pub fn add_variable(&mut self, variable_name: &str, content: VC) {
assert!(!self.variables.is_empty());
let last_block = self.variables.last_mut().unwrap();
last_block.add_variable(variable_name, content);
}
pub fn has_variable(&self, symbol: &str) -> bool {
self.block_with_variable_symbol(symbol).is_some()
}
pub fn get_variable(&self, symbol: &str) -> Option<&VC> {
let possible_block = self.block_with_variable_symbol(symbol);
if let Option::Some(block) = possible_block {
Option::Some(block.get_variable(symbol))
} else {
Option::None
}
}
pub fn get_mut_variable(&mut self, symbol: &str) -> Option<&mut VC> {
let possible_block = self.mut_block_with_variable_symbol(symbol);
if let Option::Some(block) = possible_block {
Option::Some(block.get_mut_variable(symbol))
} else {
Option::None
}
}
pub fn get_variable_res(&self, symbol: &str) -> Result<&VC, CircomEnvironmentError> {
let possible_block = self.block_with_variable_symbol(symbol);
if let Option::Some(block) = possible_block {
Result::Ok(block.get_variable(symbol))
} else {
Result::Err(CircomEnvironmentError::NonExistentSymbol)
}
}
pub fn remove_variable(&mut self, symbol: &str) {
let possible_block = self.mut_block_with_variable_symbol(symbol);
if let Option::Some(block) = possible_block {
block.remove_variable(symbol)
}
}
pub fn get_variable_or_break(&self, symbol: &str, file: &str, line: u32) -> &VC {
assert!(self.has_variable(symbol), "Method call in file {} line {}", file, line);
if let Result::Ok(v) = self.get_variable_res(symbol) {
v
} else {
unreachable!();
}
}
pub fn get_mut_variable_mut(
&mut self,
symbol: &str,
) -> Result<&mut VC, CircomEnvironmentError> {
let possible_block = self.mut_block_with_variable_symbol(symbol);
if let Option::Some(block) = possible_block {
Result::Ok(block.get_mut_variable(symbol))
} else {
Result::Err(CircomEnvironmentError::NonExistentSymbol)
}
}
pub fn get_mut_variable_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut VC {
assert!(self.has_variable(symbol), "Method call in file {} line {}", file, line);
if let Result::Ok(v) = self.get_mut_variable_mut(symbol) {
v
} else {
unreachable!();
}
}
}
impl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>
where
T: ComponentInfo,
{
pub fn add_component(&mut self, component_name: &str, content: CC) {
self.components.insert(component_name.to_string(), content);
}
pub fn remove_component(&mut self, component_name: &str) {
self.components.remove(component_name);
}
pub fn has_component(&self, symbol: &str) -> bool {
self.components.contains_key(symbol)
}
pub fn get_component(&self, symbol: &str) -> Option<&CC> {
self.components.get(symbol)
}
pub fn get_mut_component(&mut self, symbol: &str) -> Option<&mut CC> {
self.components.get_mut(symbol)
}
pub fn get_component_res(&self, symbol: &str) -> Result<&CC, CircomEnvironmentError> {
self.components.get(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_component_or_break(&self, symbol: &str, file: &str, line: u32) -> &CC {
assert!(self.has_component(symbol), "Method call in file {} line {}", file, line);
self.components.get(symbol).unwrap()
}
pub fn get_mut_component_res(
&mut self,
symbol: &str,
) -> Result<&mut CC, CircomEnvironmentError> {
self.components.get_mut(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_mut_component_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut CC {
assert!(self.has_component(symbol), "Method call in file {} line {}", file, line);
self.components.get_mut(symbol).unwrap()
}
}
impl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>
where
T: SignalInfo,
{
pub fn add_input(&mut self, input_name: &str, content: SC) {
self.inputs.insert(input_name.to_string(), content);
}
pub fn remove_input(&mut self, input_name: &str) {
self.inputs.remove(input_name);
}
pub fn add_output(&mut self, output_name: &str, content: SC) {
self.outputs.insert(output_name.to_string(), content);
}
pub fn remove_output(&mut self, output_name: &str) {
self.outputs.remove(output_name);
}
pub fn add_intermediate(&mut self, intermediate_name: &str, content: SC) {
self.intermediates.insert(intermediate_name.to_string(), content);
}
pub fn remove_intermediate(&mut self, intermediate_name: &str) {
self.intermediates.remove(intermediate_name);
}
pub fn has_input(&self, symbol: &str) -> bool {
self.inputs.contains_key(symbol)
}
pub fn has_output(&self, symbol: &str) -> bool {
self.outputs.contains_key(symbol)
}
pub fn has_intermediate(&self, symbol: &str) -> bool {
self.intermediates.contains_key(symbol)
}
pub fn has_signal(&self, symbol: &str) -> bool {
self.has_input(symbol) || self.has_output(symbol) || self.has_intermediate(symbol)
}
pub fn get_input(&self, symbol: &str) -> Option<&SC> {
self.inputs.get(symbol)
}
pub fn get_mut_input(&mut self, symbol: &str) -> Option<&mut SC> {
self.inputs.get_mut(symbol)
}
pub fn get_input_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {
self.inputs.get(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_input_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {
assert!(self.has_input(symbol), "Method call in file {} line {}", file, line);
self.inputs.get(symbol).unwrap()
}
pub fn get_mut_input_res(&mut self, symbol: &str) -> Result<&mut SC, CircomEnvironmentError> {
self.inputs.get_mut(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_mut_input_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut SC {
assert!(self.has_input(symbol), "Method call in file {} line {}", file, line);
self.inputs.get_mut(symbol).unwrap()
}
pub fn get_output(&self, symbol: &str) -> Option<&SC> {
self.outputs.get(symbol)
}
pub fn get_mut_output(&mut self, symbol: &str) -> Option<&mut SC> {
self.outputs.get_mut(symbol)
}
pub fn get_output_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {
self.outputs.get(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_output_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {
assert!(self.has_output(symbol), "Method call in file {} line {}", file, line);
self.outputs.get(symbol).unwrap()
}
pub fn get_mut_output_res(&mut self, symbol: &str) -> Result<&mut SC, CircomEnvironmentError> {
self.outputs.get_mut(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_mut_output_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut SC {
assert!(self.has_output(symbol), "Method call in file {} line {}", file, line);
self.outputs.get_mut(symbol).unwrap()
}
pub fn get_intermediate(&self, symbol: &str) -> Option<&SC> {
self.intermediates.get(symbol)
}
pub fn get_mut_intermediate(&mut self, symbol: &str) -> Option<&mut SC> {
self.intermediates.get_mut(symbol)
}
pub fn get_intermediate_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {
self.intermediates.get(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_intermediate_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {
assert!(self.has_intermediate(symbol), "Method call in file {} line {}", file, line);
self.intermediates.get(symbol).unwrap()
}
pub fn get_mut_intermediate_res(
&mut self,
symbol: &str,
) -> Result<&mut SC, CircomEnvironmentError> {
self.intermediates.get_mut(symbol).ok_or_else(|| CircomEnvironmentError::NonExistentSymbol)
}
pub fn get_mut_intermediate_or_break(
&mut self,
symbol: &str,
file: &str,
line: u32,
) -> &mut SC {
assert!(self.has_intermediate(symbol), "Method call in file {} line {}", file, line);
self.intermediates.get_mut(symbol).unwrap()
}
pub fn get_signal(&self, symbol: &str) -> Option<&SC> {
if self.has_input(symbol) {
self.get_input(symbol)
} else if self.has_output(symbol) {
self.get_output(symbol)
} else if self.has_intermediate(symbol) {
self.get_intermediate(symbol)
} else {
Option::None
}
}
pub fn get_mut_signal(&mut self, symbol: &str) -> Option<&mut SC> {
if self.has_input(symbol) {
self.get_mut_input(symbol)
} else if self.has_output(symbol) {
self.get_mut_output(symbol)
} else if self.has_intermediate(symbol) {
self.get_mut_intermediate(symbol)
} else {
Option::None
}
}
pub fn get_signal_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {
if self.has_input(symbol) {
self.get_input_res(symbol)
} else if self.has_output(symbol) {
self.get_output_res(symbol)
} else if self.has_intermediate(symbol) {
self.get_intermediate_res(symbol)
} else {
Result::Err(CircomEnvironmentError::NonExistentSymbol)
}
}
pub fn get_signal_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {
assert!(self.has_signal(symbol), "Method call in file {} line {}", file, line);
if let Result::Ok(v) = self.get_signal_res(symbol) {
v
} else {
unreachable!();
}
}
pub fn get_mut_signal_res(&mut self, symbol: &str) -> Result<&mut SC, CircomEnvironmentError> {
if self.has_input(symbol) {
self.get_mut_input_res(symbol)
} else if self.has_output(symbol) {
self.get_mut_output_res(symbol)
} else if self.has_intermediate(symbol) {
self.get_mut_intermediate_res(symbol)
} else {
Result::Err(CircomEnvironmentError::NonExistentSymbol)
}
}
pub fn get_mut_signal_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut SC {
assert!(self.has_signal(symbol), "Method call in file {} line {}", file, line);
if let Result::Ok(v) = self.get_mut_signal_res(symbol) {
v
} else {
unreachable!();
}
}
}
#[derive(Clone)]
struct VariableBlock<VC> {
variables: HashMap<String, VC>,
}
impl<VC> Default for VariableBlock<VC> {
fn default() -> Self {
VariableBlock { variables: HashMap::new() }
}
}
impl<VC> VariableBlock<VC> {
pub fn new() -> VariableBlock<VC> {
VariableBlock::default()
}
pub fn add_variable(&mut self, symbol: &str, content: VC) {
self.variables.insert(symbol.to_string(), content);
}
pub fn remove_variable(&mut self, symbol: &str) {
self.variables.remove(symbol);
}
pub fn contains_variable(&self, symbol: &str) -> bool {
self.variables.contains_key(symbol)
}
pub fn get_variable(&self, symbol: &str) -> &VC {
assert!(self.contains_variable(symbol));
self.variables.get(symbol).unwrap()
}
pub fn get_mut_variable(&mut self, symbol: &str) -> &mut VC {
assert!(self.contains_variable(symbol));
self.variables.get_mut(symbol).unwrap()
}
pub fn merge(
left: VariableBlock<VC>,
right: VariableBlock<VC>,
using: fn(VC, VC) -> VC,
) -> VariableBlock<VC> {
let left_block = left.variables;
let right_block = right.variables;
let result_block = hashmap_union(left_block, right_block, using);
VariableBlock { variables: result_block }
}
}
fn hashmap_union<K, V>(
l: HashMap<K, V>,
mut r: HashMap<K, V>,
merge_function: fn(V, V) -> V,
) -> HashMap<K, V>
where
K: Hash + Eq,
{
let mut result = HashMap::new();
for (k, v) in l {
if let Option::Some(r_v) = r.remove(&k) {
result.insert(k, merge_function(v, r_v));
} else {
result.insert(k, v);
}
}
for (k, v) in r {
result.entry(k).or_insert(v);
}
result
}

View File

@@ -0,0 +1,251 @@
use num_bigint_dig::BigInt;
use std::fmt::{Display, Formatter};
pub enum MemoryError {
OutOfBoundsError,
AssignmentError,
InvalidAccess,
UnknownSizeDimension,
}
pub type SliceCapacity = usize;
pub type SimpleSlice = MemorySlice<BigInt>;
/*
Represents the value stored in a element of a circom program.
The attribute route stores the dimensions of the slice, used to navigate through them.
The length of values is equal to multiplying all the values in route.
*/
#[derive(Eq, PartialEq)]
pub struct MemorySlice<C> {
route: Vec<SliceCapacity>,
values: Vec<C>,
}
impl<C: Clone> Clone for MemorySlice<C> {
fn clone(&self) -> Self {
MemorySlice { route: self.route.clone(), values: self.values.clone() }
}
}
impl<C: Default + Clone + Display + Eq> Display for MemorySlice<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.values.is_empty() {
f.write_str("[]")
} else if self.values.len() == 1 {
f.write_str(&format!("{}", self.values[0]))
} else {
let mut msg = format!("[{}", self.values[0]);
for i in 1..self.values.len() {
msg.push_str(&format!(",{}", self.values[i]));
}
msg.push_str("]");
f.write_str(&msg)
}
}
}
impl<C: Clone> MemorySlice<C> {
// Raw manipulations of the slice
fn get_initial_cell(
memory_slice: &MemorySlice<C>,
access: &[SliceCapacity],
) -> Result<SliceCapacity, MemoryError> {
if access.len() > memory_slice.route.len() {
return Result::Err(MemoryError::OutOfBoundsError);
}
let mut cell = 0;
let mut cell_jump = memory_slice.values.len();
let mut i: SliceCapacity = 0;
while i < access.len() {
if access[i] >= memory_slice.route[i] {
return Result::Err(MemoryError::OutOfBoundsError);
}
cell_jump /= memory_slice.route[i];
cell += cell_jump * access[i];
i += 1;
}
Result::Ok(cell)
}
// Returns the new route and the total number of cells
// that a slice with such route will have
fn generate_new_route_from_access(
memory_slice: &MemorySlice<C>,
access: &[SliceCapacity],
) -> Result<(Vec<SliceCapacity>, SliceCapacity), MemoryError> {
if access.len() > memory_slice.route.len() {
return Result::Err(MemoryError::OutOfBoundsError);
}
let mut size = Vec::new();
let mut number_of_cells = 1;
for i in access.len()..memory_slice.route.len() {
number_of_cells *= memory_slice.route[i];
size.push(memory_slice.route[i]);
}
Result::Ok((size, number_of_cells))
}
fn generate_slice_from_access(
memory_slice: &MemorySlice<C>,
access: &[SliceCapacity],
) -> Result<MemorySlice<C>, MemoryError> {
if access.is_empty() {
return Result::Ok(memory_slice.clone());
}
let (size, number_of_cells) =
MemorySlice::generate_new_route_from_access(memory_slice, access)?;
let mut values = Vec::with_capacity(number_of_cells);
let initial_cell = MemorySlice::get_initial_cell(memory_slice, access)?;
let mut offset = 0;
while offset < number_of_cells {
let new_value = memory_slice.values[initial_cell + offset].clone();
values.push(new_value);
offset += 1;
}
Result::Ok(MemorySlice { route: size, values })
}
// User operations
pub fn new(initial_value: &C) -> MemorySlice<C> {
MemorySlice::new_with_route(&[], initial_value)
}
pub fn new_array(route: Vec<SliceCapacity>, values: Vec<C>) -> MemorySlice<C> {
MemorySlice { route, values }
}
pub fn new_with_route(route: &[SliceCapacity], initial_value: &C) -> MemorySlice<C> {
let mut length = 1;
for i in route {
length *= *i;
}
let mut values = Vec::with_capacity(length);
for _i in 0..length {
values.push(initial_value.clone());
}
MemorySlice { route: route.to_vec(), values }
}
pub fn insert_values(
memory_slice: &mut MemorySlice<C>,
access: &[SliceCapacity],
new_values: &MemorySlice<C>,
) -> Result<(), MemoryError> {
let mut cell = MemorySlice::get_initial_cell(memory_slice, access)?;
if MemorySlice::get_number_of_cells(new_values)
> (MemorySlice::get_number_of_cells(memory_slice) - cell)
{
return Result::Err(MemoryError::OutOfBoundsError);
}
for value in new_values.values.iter() {
memory_slice.values[cell] = value.clone();
cell += 1;
}
Result::Ok(())
}
pub fn access_values(
memory_slice: &MemorySlice<C>,
access: &[SliceCapacity],
) -> Result<MemorySlice<C>, MemoryError> {
MemorySlice::generate_slice_from_access(memory_slice, access)
}
pub fn get_reference_to_single_value<'a>(
memory_slice: &'a MemorySlice<C>,
access: &[SliceCapacity],
) -> Result<&'a C, MemoryError> {
assert_eq!(memory_slice.route.len(), access.len());
let cell = MemorySlice::get_initial_cell(memory_slice, access)?;
Result::Ok(&memory_slice.values[cell])
}
pub fn get_mut_reference_to_single_value<'a>(
memory_slice: &'a mut MemorySlice<C>,
access: &[SliceCapacity],
) -> Result<&'a mut C, MemoryError> {
assert_eq!(memory_slice.route.len(), access.len());
let cell = MemorySlice::get_initial_cell(memory_slice, access)?;
Result::Ok(&mut memory_slice.values[cell])
}
pub fn get_number_of_cells(memory_slice: &MemorySlice<C>) -> SliceCapacity {
memory_slice.values.len()
}
pub fn route(&self) -> &[SliceCapacity] {
&self.route
}
pub fn is_single(&self) -> bool {
self.route.is_empty()
}
/*
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Calling this function with a MemorySlice !
! that has more than one cell will cause !
! the compiler to panic. Use carefully !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
pub fn unwrap_to_single(memory_slice: MemorySlice<C>) -> C {
assert!(memory_slice.is_single());
let mut memory_slice = memory_slice;
memory_slice.values.pop().unwrap()
}
pub fn destruct(self) -> (Vec<SliceCapacity>, Vec<C>) {
(self.route, self.values)
}
}
#[cfg(test)]
mod tests {
use super::*;
type U32Slice = MemorySlice<u32>;
#[test]
fn memory_slice_vector_initialization() {
let route = vec![3, 4];
let slice = U32Slice::new_with_route(&route, &0);
assert_eq!(U32Slice::get_number_of_cells(&slice), 12);
for (dim_0, dim_1) in slice.route.iter().zip(&route) {
assert_eq!(*dim_0, *dim_1);
}
for f in 0..3 {
for c in 0..4 {
let result = U32Slice::get_reference_to_single_value(&slice, &[f, c]);
if let Result::Ok(v) = result {
assert_eq!(*v, 0);
} else {
assert!(false);
}
}
}
}
#[test]
fn memory_slice_single_initialization() {
let slice = U32Slice::new(&4);
assert_eq!(U32Slice::get_number_of_cells(&slice), 1);
let memory_response = U32Slice::get_reference_to_single_value(&slice, &[]);
if let Result::Ok(val) = memory_response {
assert_eq!(*val, 4);
} else {
assert!(false);
}
}
#[test]
fn memory_slice_multiple_insertion() {
let route = vec![3, 4];
let mut slice = U32Slice::new_with_route(&route, &0);
let new_row = U32Slice::new_with_route(&[4], &4);
let res = U32Slice::insert_values(&mut slice, &[2], &new_row);
if let Result::Ok(_) = res {
for c in 0..4 {
let memory_result = U32Slice::get_reference_to_single_value(&slice, &[2, c]);
if let Result::Ok(val) = memory_result {
assert_eq!(*val, 4);
} else {
assert!(false);
}
}
} else {
assert!(false);
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod constants;
pub mod environment;
pub mod memory_slice;