/*
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 .
*/
/*
The exec functions, return an object of the form:
{
t: "C" or "V" or "S"
s: Accumulated Sizes
v: [] only when "V" Array of values where each value is:
{
t: "N", "S", "LC", "QEX", "NQ"
v: When "N" its the bigInt of the number
coefs: { sIdx: vCoef } when "LC" the values of the coefs
a,b,c: a LC object of a quadratic expression.
}
refId: In case of a variable, the reference of a variable.
sIdx: When signal, the sIdx
cIdx: When component, the cIdx
}
*/
const path = require("path");
const fs = require("fs");
const assert = require("assert");
const iterateAST = require("./iterateast");
const utils = require("./utils");
const LCAlgebra = require("./lcalgebra");
const parser = require("../parser/jaz.js").parser;
const Scalar = require("ffjavascript").Scalar;
const {stringifyBigInts} = require("ffjavascript").utils;
/* TODO: Add lines information
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;
}
*/
module.exports = constructionPhase;
const NQVAL = {t: "V", s:[1,0], v: [{t:"NQ"}]};
function constructionPhase(ctx, srcFile) {
const fullFileName = srcFile;
const fullFilePath = path.dirname(fullFileName);
const src = fs.readFileSync(fullFileName, "utf8");
ctx.ast = parser.parse(src);
assert(ctx.ast.type == "BLOCK");
ctx.lc = new LCAlgebra(ctx.F);
ctx.filePath= fullFilePath;
ctx.fileName= fullFileName;
ctx.includedFiles = {};
ctx.includedFiles[fullFileName] = src.split("\n");
ctx.refs = [];
createRefs(ctx, ctx.ast);
exec(ctx, ctx.ast);
}
function exec(ctx, ast) {
if (!ast) {
return ctx.throwError(ast, "Null AST");
}
if (ast.type == "NUMBER") {
return execNumber(ctx,ast);
} else if (ast.type == "VARIABLE") {
return execVariable(ctx, ast);
} else if (ast.type == "PIN") {
return execPin(ctx, ast);
} else if (ast.type == "OP") {
if (ast.op == "=") {
return execAssignement(ctx, ast);
} else if (ast.op == "<--") {
return execAssignement(ctx, ast);
} else if (ast.op == "<==") {
return execSignalAssignConstrain(ctx, ast);
} else if (ast.op == "===") {
return execConstrain(ctx, ast);
} else if (ast.op == "+=") {
return execOpOp(ctx, ast, "add", "LEFT");
} else if (ast.op == "*=") {
return execOpOp(ctx, ast, "mul", "LEFT");
} else if (ast.op == "+") {
return execOp(ctx, ast, "add", 2);
} else if (ast.op == "-") {
return execOp(ctx, ast, "sub", 2);
} else if (ast.op == "UMINUS") {
return execOp(ctx, ast, "neg", 1);
} else if (ast.op == "*") {
return execOp(ctx, ast, "mul", 2);
} else if (ast.op == "%") {
return execOp(ctx, ast, "mod", 2);
} else if (ast.op == "PLUSPLUSRIGHT") {
return execOpOp(ctx, ast, "add", "RIGHT");
} else if (ast.op == "PLUSPLUSLEFT") {
return execOpOp(ctx, ast, "add", "LEFT");
} else if (ast.op == "MINUSMINUSRIGHT") {
return execOpOp(ctx, ast, "sub", "RIGHT");
} else if (ast.op == "MINUSMINUSLEFT") {
return execOpOp(ctx, ast, "sub", "LEFT");
} else if (ast.op == "/") {
return execOp(ctx, ast, "div", 2);
} else if (ast.op == "\\") {
return execOp(ctx, ast, "idiv", 2);
} else if (ast.op == "**") {
return execOp(ctx, ast, "pow", 2);
} else if (ast.op == "&") {
return execOp(ctx, ast, "band", 2);
} else if (ast.op == "|") {
return execOp(ctx, ast, "bor", 2);
} else if (ast.op == "^") {
return execOp(ctx, ast, "bxor", 2);
} else if (ast.op == "~") {
return execOp(ctx, ast, "bnot", 1);
} else if (ast.op == "&&") {
return execOp(ctx, ast, "land", 2);
} else if (ast.op == "||") {
return execOp(ctx, ast, "lor", 2);
} else if (ast.op == "!") {
return execOp(ctx, ast, "lnot", 1);
} else if (ast.op == "<<") {
return execOp(ctx, ast, "shl", 2);
} else if (ast.op == ">>") {
return execOp(ctx, ast, "shr", 2);
} else if (ast.op == "<") {
return execOp(ctx, ast, "lt", 2);
} else if (ast.op == ">") {
return execOp(ctx, ast, "gt", 2);
} else if (ast.op == "<=") {
return execOp(ctx, ast, "leq", 2);
} else if (ast.op == ">=") {
return execOp(ctx, ast, "geq", 2);
} else if (ast.op == "==") {
return execOp(ctx, ast, "eq", 2);
} else if (ast.op == "!=") {
return execOp(ctx, ast, "neq", 2);
} else if (ast.op == "?") {
return execTerCon(ctx, ast);
} else {
ctx.throwError(ast, "Invalid operation: " + ast.op);
}
} else if (ast.type == "DECLARE") {
if (ast.declareType == "COMPONENT") {
return execDeclareComponent(ctx, ast);
} else if ((ast.declareType == "SIGNALIN")||
(ast.declareType == "SIGNALOUT")||
(ast.declareType == "SIGNAL")) {
return execDeclareSignal(ctx, ast);
} else if (ast.declareType == "VARIABLE") {
return execDeclareVariable(ctx, ast);
} else {
ctx.throwError(ast, "Invalid declaration: " + ast.declareType);
}
} else if (ast.type == "FUNCTIONCALL") {
return execFunctionCall(ctx, ast);
} else if (ast.type == "BLOCK") {
return execBlock(ctx, ast);
} else if (ast.type == "COMPUTE") {
return execCompute(ctx, ast);
} else if (ast.type == "FOR") {
return execLoop(ctx, ast);
} else if (ast.type == "WHILE") {
return execLoop(ctx, ast);
} else if (ast.type == "IF") {
return execIf(ctx, ast);
} else if (ast.type == "RETURN") {
return execReturn(ctx, ast);
} else if (ast.type == "TEMPLATEDEF") {
return execTemplateDef(ctx, ast);
} else if (ast.type == "FUNCTIONDEF") {
return execFunctionDef(ctx, ast);
} else if (ast.type == "INCLUDE") {
return execInclude(ctx, ast);
} else if (ast.type == "ARRAY") {
return execArray(ctx, ast);
} else {
ctx.throwError(ast, "Invalid AST node type: " + ast.type);
}
}
function execNumber(ctx, ast) {
return {
t: "V",
s:[1,0],
v: [{
t: "N",
v: ctx.F.e(ast.value)
}]
};
}
function execTemplateDef(ctx, ast) {
ctx.templates[ast.name] = {
block: ast.block,
params: ast.params,
fileName: ctx.fileName,
filePath: ctx.filePath,
};
}
function execFunctionDef(ctx, ast) {
ctx.functions[ast.name] = {
block: ast.block,
params: ast.params,
fileName: ctx.fileName,
filePath: ctx.filePath
};
}
function execDeclareComponent(ctx, ast) {
if (ast.name.type != "VARIABLE") return ctx.throwError( ast, "Invalid component name");
const sizes=[];
for (let i=0; i< ast.name.selectors.length; i++) {
const sizeRef = exec(ctx, ast.name.selectors[i]);
if (ctx.error) return;
const size = val(ctx, sizeRef);
if (size.t != "N") return ctx.throwError( ast.name.selectors[i], "expected a number");
sizes.push( Scalar.toNumber(size.v) );
}
let cIdx = ctx.addComponent(ast.name.name, sizes);
if (!Array.isArray(cIdx)) cIdx = [cIdx, cIdx+1];
ctx.refs[ast.refId].s = utils.accSizes(sizes);
ctx.refs[ast.refId].cIdx = cIdx[0];
return ctx.refs[ast.refId];
}
function execDeclareSignal(ctx, ast) {
if (ast.name.type != "VARIABLE") return ctx.throwError(ast, "Invalid component name");
const sizes=[];
for (let i=0; i< ast.name.selectors.length; i++) {
const size = exec(ctx, ast.name.selectors[i]);
if (ctx.error) return;
if (size.s[0] != 1) return ctx.throwError(ast, "Size cannot be an array");
if (size.v[0].t != "N") return ctx.throwError(ast, "Size must be declared in construction time");
sizes.push( Scalar.toNumber(size.v[0].v) );
}
let sIdx = ctx.addSignal(ast.name.name, sizes);
if (!Array.isArray(sIdx)) sIdx = [sIdx, sIdx+1];
for (let i=sIdx[0]; i", "==>"].indexOf(ast.op) < 0)) return ctx.throwError(ast, "Cannot assign to a signal with `=` use <-- or <== ops");
if ((left.t == "V")&&( ["<--", "<==", "-->", "==>"].indexOf(ast.op) >= 0)) return ctx.throwError(ast, `Cannot assign to a var with ${ast.op}. use = op`);
const right = exec(ctx, ast.values[1]);
if (ctx.error) return;
if (!utils.sameSizes(left.s.slice(leftSels.length),right.s)) return ctx.throwError(ast, "Sizes in assignment must be the same");
let o = 0;
for (let i=0; i=0) sDest=ctx.signals[sDest.e];
if (utils.isDefined(sDest.v)) return ctx.throwError(ast, "Signals cannot be assigned twice");
if (v !== null) {
sDest.v = v;
}
}
function joinSignals(dIdx, sIdx) {
let sDest=ctx.signals[dIdx];
let isOut = (sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT);
while (sDest.e>=0) {
sDest=ctx.signals[sDest.e];
isOut = isOut || ((sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT));
}
if (utils.isDefined(sDest.v)) return ctx.throwError(ast, "Signals cannot be assigned twice");
let sSrc = ctx.signals[sIdx];
let isIn = (sSrc.o & ctx.MAIN)&&(sSrc.o & ctx.IN);
while (sSrc.e>=0) {
sSrc=ctx.signals[sSrc.e];
isIn = isIn || ((sSrc.o & ctx.MAIN)&&(sSrc.o & ctx.IN));
}
// Skip if an out is assigned directly to an input.
if ((!isIn)||(!isOut)) {
sDest.e = sIdx;
} else {
if (utils.isDefined(sSrc.v)) sDest.v = sSrc.v;
}
}
}
function execInstantiateComponet(ctx, vr, fn, sels) {
if (fn.type != "FUNCTIONCALL") return ctx.throwError(fn, "Right type of instantiate component must be a function call");
const templateName = fn.name;
const template = ctx.templates[templateName];
if (!template) return ctx.throwError(fn, "Invalid Template");
const paramValues = [];
for (let i=0; i< fn.params.length; i++) {
const v = exec(ctx, fn.params[i]);
if (ctx.error) return;
for (let j=0; j v.s.length-2) return ctx.throwError(ast, "Too many selectors");
for (let i=0; i= v.s[i] / v.s[i+1]) return ctx.throwError(ast, "Out of Range");
if (sels[i] < 0 ) return ctx.throwError(ast, "selector negative");
o += sels[i] * v.s[i+1];
s = v.s[i+1];
}
if (v.t == "V") {
return {
t: "V",
s: v.s.slice(sels.length),
v: v.v.slice(o, o+s)
};
} else if (v.t == "S") {
return {
t: "S",
s: v.s.slice(sels.length),
sIdx: o + v.sIdx
};
} else if (v.t == "C") {
return {
t: "C",
s: v.s.slice(sels.length),
cIdx: o
};
} else {
assert(false);
}
}
function execPin(ctx, ast) {
const selsC = [];
for (let i=0; i< ast.component.selectors.length; i++) {
const sel = exec(ctx, ast.component.selectors[i]);
if (ctx.error) return;
if (sel.s[0] != 1) return ctx.throwError(ast, "Component selector cannot be an array");
if (sel.v[0].t != "N") return NQVAL;
selsC.push(Scalar.toNumber(sel.v[0].v));
}
const cIdx = ctx.getComponentIdx(ast.component.name, selsC);
if (cIdx<0) return ctx.throwError(ast.component, "Component does not exists: "+ast.component.name);
const selsP = [];
for (let i=0; i< ast.pin.selectors.length; i++) {
const sel = exec(ctx, ast.pin.selectors[i]);
if (ctx.error) return;
if (sel.s[0] != 1) return ctx.throwError(ast, "Signal selector cannot be an array");
if (sel.v[0].t != "N") return NQVAL;
selsP.push(Scalar.toNumber(sel.v[0].v));
}
if (!ctx.components[cIdx]) {
return ctx.throwError(ast, "Component not defined yet");
}
const sIdx = ctx.components[cIdx].names.getSignalIdx(ast.pin.name, selsP);
if (sIdx<0) return ctx.throwError(ast, "Signal not defined:" + buildFullName() );
return {
t: "S",
sIdx: sIdx,
s: utils.accSizes(ctx.components[cIdx].names.o[ast.pin.name].sizes).slice(selsP.length)
};
function buildFullName() {
return ast.component.name + sels2str(selsC) + "." + ast.pin.name + sels2str(selsP);
}
function sels2str(sels) {
let S = "";
for (let i=0; i< sels.length; i++) {
S += "[" + sels[i] + "]";
}
return S;
}
}
function execLoop(ctx, ast) {
if (ast.init) {
exec(ctx, ast.init);
if (ctx.error) return;
}
let v = exec(ctx, ast.condition);
if (ctx.error) return;
if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition in loop cannot be an array");
if (v.v[0].t != "N") {
iterateAST(ast, (ast) => {
if (ast.type == "OP") {
if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
return ctx.throwError(ast.condition, "Constraint inside a calculating block");
}
}
});
// Assert no constraints
return;
}
while ((! ctx.F.isZero(v.v[0].v))&&(!ctx.returnValue)) {
exec(ctx, ast.body);
if (ctx.error) return;
if (ast.step) {
exec(ctx, ast.step);
if (ctx.error) return;
}
v = exec(ctx, ast.condition);
if (ctx.error) return;
if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition in loop cannot be an array");
if (v.v[0].t != "N") return ctx.throwError(ast.condition, "Condition result not a number");
}
}
function execCompute(ctx, ast) {
iterateAST(ast, (ast) => {
if (ast.type == "OP") {
if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
return ctx.throwError(ast.condition, "Constraint inside a calculating block");
}
}
});
}
function execIf(ctx, ast) {
let v = exec(ctx, ast.condition);
if (ctx.error) return;
if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition cannot be an array");
if (v.v[0].t != "N") {
iterateAST(ast, (ast) => {
if (ast.type == "OP") {
if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
return ctx.throwError(ast.condition, "Constraint inside a calculating block");
}
}
});
// Assert no constraints
return;
}
if (!ctx.F.isZero(v.v[0].v)) {
exec(ctx, ast.then);
} else {
if (ast.else) {
exec(ctx, ast.else);
}
}
}
function execTerCon(ctx, ast) {
let v = exec(ctx, ast.values[0]);
if (ctx.error) return;
if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition cannot be an array");
if (v.v[0].t != "N") {
iterateAST(ast, (ast) => {
if (ast.type == "OP") {
if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
return ctx.throwError(ast.condition, "Constraint inside a calculating block");
}
}
});
// Assert no constraints
return NQVAL;
}
if (!ctx.F.isZero(v.v[0].v)) {
return exec(ctx, ast.values[1]);
} else {
return exec(ctx, ast.values[2]);
}
}
function execOp(ctx, ast, op, nOps) {
const operands = [];
for (let i=0; i= 0) sIdx = ctx.signals[sIdx].e;
res.coefs[sIdx] = ctx.F.one;
return res;
} else {
ctx.throwError(ast, "Invalid type: " + a.t);
}
}
function execConstrain(ctx, ast) {
ast.fileName = ctx.fileName;
ast.filePath = ctx.filePath;
const a = exec(ctx, ast.values[0]);
if (ctx.error) return;
const aV = val(ctx, a, ast.values[0]);
const b = exec(ctx, ast.values[1]);
if (ctx.error) return;
const bV = val(ctx, b, ast.values[1]);
const res = ctx.lc.sub(aV,bV);
if (res.type == "NQ") return ctx.throwError(ast, "Non Quadratic constraint");
if (!ctx.lc.isZero(res)) {
ctx.constraints.push(ctx.lc.toQEX(res));
if (ctx.verbose) {
if ((ctx.constraints.length % 10000 == 0)&&(ctx.constraints.length>0)) console.log("Constraints: " + ctx.constraints.length);
}
}
return a;
}
function execSignalAssignConstrain(ctx, ast) {
const v = execAssignement(ctx,ast);
if (ctx.error) return;
execConstrain(ctx, ast);
if (ctx.error) return;
return v;
}
function execInclude(ctx, ast) {
if (ast.block) {
const oldFilePath = ctx.filePath;
const oldFileName = ctx.fileName;
ctx.filePath = ast.filePath;
ctx.fileName = ast.fileName;
exec(ctx, ast.block); // Process only one.
ctx.filePath = oldFilePath;
ctx.fileName = oldFileName;
}
}
function execArray(ctx, ast) {
const res = {t: "V", v:[]};
let subSize = null;
for (let i=0; i {
while ((scopeLabels.length>0)&&(!level.startsWith(scopeLabels[scopeLabels.length-1]))) {
scopes.pop();
scopeLabels.pop();
}
if (ast.type == "DECLARE") {
if (ast.declareType == "COMPONENT") {
ast.refId = define(ast.name.name, {t: "C"});
} else if ((ast.declareType == "SIGNALIN")||
(ast.declareType == "SIGNALOUT")||
(ast.declareType == "SIGNAL")) {
ast.refId = define(ast.name.name, {t: "S"});
} else if (ast.declareType == "VARIABLE") {
ast.refId = define(ast.name.name, {t: "V", s: null});
} else {
return ctx.throwError(ast, "Invalid declaration: " + ast.declareType);
}
} else if (["BLOCK", "FOR", "IF", "WHILE", "COMPUTE"].indexOf(ast.type) >= 0) {
scopeLabels.push(level);
scopes.push({});
} else if (ast.type == "TEMPLATEDEF") {
ast.refId = define(ast.name, {t: "T"});
} else if (ast.type == "FUNCTIONDEF") {
ast.refId = define(ast.name, {t: "F"});
} else if (ast.type == "INCLUDE") {
const incFileName = path.resolve(ctx.filePath, ast.file);
const incFilePath = path.dirname(incFileName);
ctx.includedFiles = ctx.includedFiles || [];
if (ctx.includedFiles[incFileName]) {
ctx.bloc = null; // Include already included...
return;
}
const src = fs.readFileSync(incFileName, "utf8");
if (!src) return ctx.throwError(ast, "Include file not found: "+incFileName);
ctx.includedFiles[incFileName] = src.split("\n");
ast.block = parser.parse(src);
ast.filePath = incFilePath;
ast.fileName = incFileName;
const oldFilePath = ctx.filePath;
const oldFileName = ctx.fileName;
ctx.filePath = incFilePath;
ctx.fileName = incFileName;
scopes.push({});
createRefs(ctx, ast.block);
scopes.pop();
ctx.filePath = oldFilePath;
ctx.fileName = oldFileName;
} else if (ast.type == "VARIABLE") {
ast.refId = name2ref(ast.name);
}
function define(name, entry) {
if (scopes[scopes.length-1][name]) return ctx.throwError(ast, `Name already defined: ${name}`);
for (let i=scopes.length-2; i>=0; i--) {
if (scopes[i][name]) ctx.logWarning(ast, `Shadowing variable: ${name}`);
}
const refId = ctx.refs.length;
entry.refId = refId;
ctx.refs.push(entry);
scopes[scopes.length-1][name] = refId;
return refId;
}
function name2ref(n) {
for (let i=scopes.length-1; i>=0; i--) {
if (typeof scopes[i][n] !== "undefined") return scopes[i][n];
}
return -1;
}
});
}