/* Copyright 2018 0KIMS association. This file is part of circom (Zero Knowledge Circuit Compiler). circom is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. circom is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with circom. If not, see . */ /* description: Construct AST for jaz language. */ /* lexical grammar */ %lex %% \s+ { /* skip whitespace */ } \/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\/ { /* console.log("MULTILINE COMMENT: "+yytext); */ } \/\/.* { /* console.log("SINGLE LINE COMMENT: "+yytext); */ } var { return 'var'; } signal { return 'signal'; } private { return 'private'; } input { return 'input'; } output { return 'output'; } linearCombination { return 'linearCombination'; } component { return 'component'; } template { return 'template'; } function { return 'function'; } if { return 'if'; } else { return 'else'; } for { return 'for'; } while { return 'while'; } do { return 'do'; } return { return 'return'; } include { return 'include'; } 0x[0-9A-Fa-f]* { return 'HEXNUMBER'; } [0-9]+ { return 'DECNUMBER'; } [a-zA-Z][a-zA-Z$_0-9]* { return 'IDENTIFIER'; } \"[^"]+\" { yytext = yytext.slice(1,-1); return 'STRING'; } \=\=\> { return '==>'; } \<\=\= { return '<=='; } \-\-\> { return '-->'; } \<\-\- { return '<--'; } \=\=\= { return '==='; } \>\>\= { return '>>='; } \<\<\= { return '<<='; } \&\& { return '&&'; } \|\| { return '||'; } \=\= { return '=='; } \<\= { return '<='; } \>\= { return '>='; } \!\= { return '!='; } \>\> { return '>>'; } \<\< { return '<<'; } \*\* { return '**'; } \+\+ { return '++'; } \-\- { return '--'; } \+\= { return '+='; } \-\= { return '-='; } \*\= { return '*='; } \/\= { return '/='; } \%\= { return '%='; } \|\= { return '|='; } \&\= { return '&='; } \^\= { return '^='; } \= { return '='; } \+ { return '+'; } \- { return '-'; } \* { return '*'; } \/ { return '/'; } \% { return '%'; } \^ { return '^'; } \& { return '&'; } \| { return '|'; } \! { return '!'; } \< { return '<'; } \> { return '>'; } \! { return '!'; } \? { return '?'; } \: { return ':'; } \( { return '('; } \) { return ')'; } \[ { return '['; } \] { return ']'; } \{ { return '{'; } \} { return '}'; } \; { return ';'; } \, { return ','; } \. { return '.'; } <> { return 'EOF'; } . { console.log("INVALID: " + yytext); return 'INVALID'} /lex %left ';' %right 'if' 'else' %left EMPTY %left IDLIST %left ',' %right '?' ':' TERCOND '=' '+=' '-=' '*=' '/=' '%=' '>>=' '<<=' '&=' '|=' '^=' '<==' '==>' '===' '<--' '-->' %left '||' %left '&&' %left '|' %left '^' %left '&' %left '==' '!=' %left '<=' '>=' '<' '>' %left '<<' '>>' %left '+' '-' %left '*' '/' '%' %left '**' %right '++' '--' UMINUS UPLUS '!' '~' %left '.' %left DECL %left PLUSPLUSRIGHT MINUSMINUSRIGHT '[' ']' '(' ')' %left HIGH %{ const bigInt = require('big-integer'); const util = require('util'); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const __MASK__ = new bigInt(2).pow(253).minus(1); function setLines(dst, first, last) { last = last || first; dst.first_line = first.first_line; dst.first_column = first.first_column; dst.last_line = last.last_line; dst.last_column = last.last_column; } %} %start allStatments %% /* language grammar */ allStatments : statmentList EOF { // console.log(JSON.stringify($1, null, 1)); $$ = { type: "BLOCK", statements: $1.statments }; setLines($$, @1); return $$ } ; statmentList : statmentList statment { $1.statments.push($2); setLines($1, @1, @2); } | statment { $$ = { type: "STATMENTLIST", statments: [$1] }; setLines($$, @1); } ; statment : functionDefinitionStatment { $$ = $1; } | templateDefinitionStatment { $$ = $1; } | ifStatment { $$ = $1; } | forStatment { $$ = $1; } | whileStatment { $$ = $1; } | doWhileStatment { $$ = $1; } | returnStatment { $$ = $1; } | block { $$ = $1; } | expressionStatment { $$ = $1; } | includeStatment { $$ = $1; } ; functionDefinitionStatment : 'function' IDENTIFIER '(' identifierList ')' block { $$ = { type: "FUNCTIONDEF", name: $2, params: $4.identifiers, block: $6}; setLines($$, @1, @6); } | 'function' IDENTIFIER '(' ')' block { $$ = { type: "FUNCTIONDEF", name: $2, params: [], block: $5 }; setLines($$, @1, @5); } ; templateDefinitionStatment : 'template' IDENTIFIER '(' identifierList ')' block { $$ = { type: "TEMPLATEDEF", name: $2, params: $4.identifiers, block: $6 }; setLines($$, @1, @6); } | 'template' IDENTIFIER '(' ')' block { $$ = { type: "TEMPLATEDEF", name: $2, params: [], block: $5 }; setLines($$, @1, @5); } ; identifierList : identifierList ',' IDENTIFIER { $1.identifiers.push($3); setLines($1, @1, @3); } | IDENTIFIER %prec EMPTY { $$ = { type: "IDENTIFIERLIST", identifiers: [$1] }; setLines($$, @1); } ; ifStatment : 'if' '(' expression ')' statment 'else' statment { if ($3.type == "NUMBER") { $$ = !$3.value.eq(0) ? $5 : $7; } else { $$ = { type: "IF", condition: $3, then: $5, else: $7 }; } setLines($$, @1, @7); } | 'if' '(' expression ')' statment { if ($3.type == "NUMBER") { $$ = !$3.value.eq(0) ? $5 : { type: "NUMBER", value: bigInt(0) }; } else { $$ = { type: "IF", condition: $3, then: $5 }; } setLines($$, @1, @5); } ; forStatment : 'for' '(' expression ';' expression ';' expression ')' statment { $$ = { type: "FOR", init: $3, condition: $5, step: $7, body: $9 }; setLines($$, @1, @9); } ; whileStatment : 'while' '(' expression ')' statment { $$ = { type: "WHILE", condition: $3, body: $5 }; setLines($$, @1, @5); } ; doWhileStatment : 'do' statment 'while' '(' expression ')' { $$ = { type: "DOWHILE", condition: $5, body: $2 }; setLines($$, @1, @6); } ; returnStatment : 'return' expression ';' { $$ = { type: "RETURN", value: $2 }; setLines($$, @1, @3); } | 'return' expression %prec ';' { $$ = { type: "RETURN", value: $2 } setLines($$, @1, @2); } ; includeStatment : 'include' STRING ';' { $$ = { type: "INCLUDE", file: $2 }; setLines($$, @1, @3); } | 'include' STRING %prec ';' { $$ = { type: "INCLUDE", file: $2 } setLines($$, @1, @2); } ; block : '{' statmentList '}' { $$ = { type: "BLOCK", statements: $2.statments }; setLines($$, @1, @3); } ; expressionStatment : expression ';' %prec ';' { $$ = $1; } | expression %prec ';' { $$ = $1; } ; expression : e17 %prec EMPTY { $$ = $1; } ; e17 : leftHandExpression '=' e17 { $$ = { type: "OP", op: "=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '+=' e17 { $$ = { type: "OP", op: "+=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '-=' e17 { $$ = { type: "OP", op: "-=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '*=' e17 { $$ = { type: "OP", op: "*=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '/=' e17 { $$ = { type: "OP", op: "/=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '%=' e17 { $$ = { type: "OP", op: "%=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '<<=' e17 { $$ = { type: "OP", op: "<<=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '>>=' e17 { $$ = { type: "OP", op: ">>=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '&=' e17 { $$ = { type: "OP", op: "&=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '|=' e17 { $$ = { type: "OP", op: "|=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '^=' e17 { $$ = { type: "OP", op: "^=", values: [$1, $3] }; setLines($$, @1, @3); } | leftHandExpression '<==' e17 { $$ = { type: "OP", op: "<==", values: [$1, $3] }; setLines($$, @1, @3); } | e17 '==>' leftHandExpression { $$ = { type: "OP", op: "<==", values: [$3, $1] }; setLines($$, @1, @3); } | leftHandExpression '<--' e17 { $$ = { type: "OP", op: "<--", values: [$1, $3] }; setLines($$, @1, @3); } | e17 '-->' leftHandExpression { $$ = { type: "OP", op: "<--", values: [$3, $1] }; setLines($$, @1, @3); } | e16 '===' e17 { $$ = { type: "OP", op: "===", values: [$1, $3] }; setLines($$, @1, @3); } | e17 '?' e17 ':' e17 %prec TERCOND { if ($1.type == "NUMBER") { $$ = !$1.value.eq(0) ? $3 : $5; } else { $$ = { type: "OP", op: "?", values: [$1, $3, $5] }; } setLines($$, @1, @5); } | e16 %prec EMPTY { $$ = $1; } ; e16 : rightArray { $$ = $1; } | e15 %prec EMPTY { $$ = $1; } ; e15 : e15 '||' e14 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: !$1.value.eq(0) || !$3.value.eq(0) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: "||", values: [$1, $3] }; } setLines($$, @1, @3); } | e14 %prec EMPTY { $$ = $1; } ; e14 : e14 '&&' e13 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: !$1.value.eq(0) && !$3.value.eq(0) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: "&&", values: [$1, $3] }; } setLines($$, @1, @3); } | e13 %prec EMPTY { $$ = $1; } ; e13 : e13 '|' e12 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.or($3.value).and(__MASK__) }; } else { $$ = { type: "OP", op: "|", values: [$1, $3] }; } setLines($$, @1, @3); } | e12 %prec EMPTY { $$ = $1; } ; e12 : e12 '^' e11 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.or($3.value).and(__MASK__) }; } else { $$ = { type: "OP", op: "^", values: [$1, $3] }; } setLines($$, @1, @3); } | e11 %prec EMPTY { $$ = $1; } ; e11 : e11 '&' e10 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.and($3.value).and(__MASK__) }; } else { $$ = { type: "OP", op: "&", values: [$1, $3] }; } setLines($$, @1, @3); } | e10 %prec EMPTY { $$ = $1; } ; e10 : e10 '==' e9 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.equals($3.value) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: "==", values: [$1, $3] }; } setLines($$, @1, @3); } | e10 '!=' e9 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.eq($3.value) ? bigInt(0) : bigInt(1) }; } else { $$ = { type: "OP", op: "!=", values: [$1, $3] }; } setLines($$, @1, @3); } | e9 %prec EMPTY { $$ = $1 } ; e9 : e9 '<=' e7 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.lesserOrEquals($3.value) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: "<=", values: [$1, $3] }; } setLines($$, @1, @3); } | e9 '>=' e7 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.greaterOrEquals($3.value) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: ">=", values: [$1, $3] }; } setLines($$, @1, @3); } | e9 '<' e7 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.lesser($3.value) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: "<", values: [$1, $3] }; } setLines($$, @1, @3); } | e9 '>' e7 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.greater($3.value) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: ">", values: [$1, $3] }; } setLines($$, @1, @3); } | e7 %prec EMPTY { $$ = $1 } ; e7 : e7 '<<' e6 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { let v = $3.value.greater(256) ? 256 : $3.value.value; $$ = { type: "NUMBER", value: $1.value.shiftLeft(v).and(__MASK__) }; } else { $$ = { type: "OP", op: "<<", values: [$1, $3] }; } setLines($$, @1, @3); } | e7 '>>' e6 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { let v = $3.value.greater(256) ? 256 : $3.value.value; $$ = {t1ype: "NUMBER", value: $1.value.shiftRight(v).and(__MASK__) }; } else { $$ = { type: "OP", op: ">>", values: [$1, $3] }; } setLines($$, @1, @3); } | e6 %prec EMPTY { $$ = $1; } ; e6 : e6 '+' e5 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: ($1.value.plus($3.value)).mod(__P__) }; } else { $$ = { type: "OP", op: "+", values: [$1, $3] }; } setLines($$, @1, @3); } | e6 '-' e5 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: ($1.value.plus(__P__).minus($3.value)).mod(__P__) }; } else { $$ = { type: "OP", op: "-", values: [$1, $3] }; } setLines($$, @1, @3); } | e5 %prec EMPTY { $$ = $1; } ; e5 : e5 '*' e4 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: ($1.value.times($3.value)).mod(__P__) }; } else { $$ = { type: "OP", op: "*", values: [$1, $3] }; } setLines($$, @1, @3); } | e5 '/' e4 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: ($1.value.times($3.value.modInv(__P__))).mod(__P__) }; } else { $$ = { type: "OP", op: "/", values: [$1, $3] }; } setLines($$, @1, @3); } | e5 '%' e4 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.mod($3.value) }; } else { $$ = { type: "OP", op: "%", values: [$1, $3] }; } setLines($$, @1, @3); } | e4 %prec EMPTY { $$ = $1; } ; e4 : e4 '**' e3 { if (($1.type == "NUMBER") && ($3.type == "NUMBER")) { $$ = { type: "NUMBER", value: $1.value.modPow($3.value, __P__) }; } else { $$ = { type: "OP", op: "**", values: [$1, $3] }; } setLines($$, @1, @3); } | e3 %prec EMPTY { $$ = $1; } ; e3 : '++' leftHandExpression { $$ = { type: "OP", op: "PLUSPLUSLEFT", values: [$2] }; setLines($$, @1, @2); } | '--' leftHandExpression { $$ = { type: "OP", op: "MINUSMINUSLEFT", values: [$2] }; setLines($$, @1, @2); } | '+' e3 %prec UPLUS { $$ = $2; setLines($$, @1, @2); } | '-' e3 %prec UMINUS { if ($2.type == "NUMBER") { $$ = { type: "NUMBER", value: __P__.minus($2.value).mod(__P__) }; } else { $$ = { type: "OP", op: "UMINUS", values: [$2] }; } setLines($$, @1, @2); } | '!' e3 { if ($2.type == "NUMBER") { $$ = { type: "NUMBER", value: $2.value.eq(0) ? bigInt(1) : bigInt(0) }; } else { $$ = { type: "OP", op: "!", values: [$2] }; } setLines($$, @1, @2); } | '~' e3 { if ($2.type == "NUMBER") { $$ = { type: "NUMBER", value: $2.value.xor(__MASK__) }; } else { $$ = { type: "OP", op: "~", values: [$2] }; } setLines($$, @1, @2); } | e2 %prec EMPTY { $$ = $1; } ; e2 : leftHandExpression '++' %prec PLUSPLUSRIGHT { $$ = {type: "OP", op: "PLUSPLUSRIGHT", values: [$1] }; setLines($$, @1, @2); } | leftHandExpression '--' %prec MINUSMINUSRIGHT { $$ = {type: "OP", op: "MINUSMINUSRIGHT", values: [$1] }; setLines($$, @1, @2); } | functionCall { $$ = $1; } | e0 %prec EMPTY { $$ = $1; } ; e0 : leftHandExpression %prec EMPTY { $$ = $1 } | DECNUMBER { $$ = {type: "NUMBER", value: bigInt($1).mod(__P__) } setLines($$, @1); } | HEXNUMBER { $$ = {type: "NUMBER", value: bigInt($1.substr(2).toUpperCase(), 16).mod(__P__) } setLines($$, @1); } | '(' expression ')' %prec EMPTY { $$ = $2; setLines($$, @1, @3); } ; leftHandExpression : simpleLeftHandExpression '.' simpleLeftHandExpression %prec EMPTY { $$ = {type: "PIN", component: $1, pin: $3 }; setLines($$, @1, @3); } | declaration %prec DECL { $$ = $1 } | simpleLeftHandExpression %prec EMPTY { $$ = $1 } ; declaration : 'var' simpleLeftHandExpression %prec DECL { $$ = {type: "DECLARE", declareType: "VARIABLE", name: $2} setLines($$, @1, @2); } | 'signal' simpleLeftHandExpression %prec DECL { $$ = {type: "DECLARE", declareType: "SIGNAL", name: $2} setLines($$, @1, @2); } | 'signal' 'input' simpleLeftHandExpression %prec DECL { $$ = {type: "DECLARE", declareType: "SIGNALIN", name: $3}; setLines($$, @1, @3); } | 'signal' 'private' 'input' simpleLeftHandExpression %prec DECL { $$ = {type: "DECLARE", declareType: "SIGNALIN", private: true, name: $4}; setLines($$, @1, @4); } | 'signal' 'output' simpleLeftHandExpression %prec DECL { $$ = {type: "DECLARE", declareType: "SIGNALOUT", name: $3}; setLines($$, @1, @3); } | 'component' simpleLeftHandExpression %prec DECL { $$ = {type: "DECLARE", declareType: "COMPONENT", name: $2} setLines($$, @1, @2); } ; simpleLeftHandExpression : simpleLeftHandExpression array { for (let i=0; i< $2.values.length; i++) { $1.selectors.push($2.values[i]); } setLines($1, @1, @2); } | IDENTIFIER %prec EMPTY { $$ = {type: "VARIABLE", name: $1 , selectors: []}; setLines($$, @1); } ; functionCall : IDENTIFIER '(' expressionList ')' { $$ = {type: "FUNCTIONCALL", name: $1, params: $3.expressions} setLines($$, @1, @4); } | IDENTIFIER '(' ')' { $$ = {type: "FUNCTIONCALL", name: $1, params: []} setLines($$, @1, @3); } ; expressionList : expressionList ',' expression { $1.expressions.push($3); setLines($$, @1, @3); } | expression %prec ',' { $$ = {type: "EXPRESSIONLST", expressions: [$1]}; setLines($$, @1); } ; rightArray : array %prec EMPTY { $$ = $1; } ; array : '[' expressionList ']' { $$ = { type: "ARRAY", values: $2.expressions}; setLines($$, @1, @3); } ;