|
|
/* 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 <https://www.gnu.org/licenses/>.
*/
/*
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<sIdx[1]; i++) { ctx.signals[i] = { o: 0, e: -1 };
if (ast.declareType == "SIGNALIN") { ctx.signals[i].o |= ctx.IN; ctx.components[ctx.currentComponent].nInSignals+=1; } if (ast.declareType == "SIGNALOUT") { ctx.signals[i].o |= ctx.OUT; } if (ast.private ) { ctx.signals[i].o |= ctx.PRV; } if (ctx.main) { ctx.signals[i].o |= ctx.MAIN; }
// ctx.components[ctx.currentComponent].signals.push(i);
}
const v = ctx.refs[ast.refId]; v.s = utils.accSizes(sizes); v.sIdx = sIdx[0];
return v; }
function execDeclareVariable(ctx, ast) {
if (ast.name.type != "VARIABLE") return ctx.throwError(ast, "Invalid linear combination 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) ); }
const v = ctx.refs[ast.refId];
v.s = utils.accSizes(sizes); v.v = new Array(v.s[0]);
return v; }
function execAssignement(ctx, ast) { let left; const leftSels=[]; if (ast.values[0].type == "DECLARE") { left = exec(ctx, ast.values[0]); if (ctx.error) return; } else if (ast.values[0].type == "PIN") { left = execPin(ctx, ast.values[0]); if (ctx.error) return; } else { if (!utils.isDefined(ast.values[0].refId)) return ctx.throwError(ast, "Assigning to a non variable"); left = ctx.refs[ast.values[0].refId]; if (ast.values[0].selectors) { for (let i=0; i< ast.values[0].selectors.length; i++) { const sel = exec(ctx, ast.values[0].selectors[i]); if (ctx.error) return;
if (sel.s[0] != 1) return ctx.throwError(ast, "Selector cannot be an array"); if (sel.v[0].t != "N") return {t: "NQ"};
leftSels.push( Scalar.toNumber(sel.v[0].v) ); } }
}
if ((!left)||(!left.s)) return ctx.throwError(ast, "variable. not defined yet");
if (left.t == "C") return execInstantiateComponet(ctx, left, ast.values[1], leftSels); if ((left.t == "S")&&( ["<--", "<==", "-->", "==>"].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<leftSels.length; i++) o += leftSels[i]*left.s[i+1]; if (left.t == "V") { if (right.t=="V") { for (let i=0; i<right.s[0]; i++) { left.v[o+i]=right.v[i]; } } else if (right.t == "S") { for (let i=0; i<right.s[0]; i++) { left.v[o+i]={t: "LC", coefs: {}}; left.v[o+i].coefs[right.sIdx+i] = ctx.F.one; } } } else if ( left.t == "S") { if (right.t=="V") { for (let i=0; i<right.s[0]; i++) { const ev = ctx.lc.evaluate(ctx, right.v[i]); setSignalValue(left.sIdx + o +i, ev); } } else if (right.t == "S") { for (let i=0; i<right.s[0]; i++) { joinSignals(left.sIdx + o + i, right.sIdx + i); } } }
return right;
function setSignalValue(dSIdx, v) { let sDest = ctx.signals[dSIdx]; while (sDest.e>=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[0]; j++) { if (v.v[j].t != "N") ctx.throwError(fn, "Parameters of a template must be constant"); } paramValues.push(v); } if (template.params.length != paramValues.length) ctx.throwError(fn, "Invalid Number of parameters");
let o=0; for (let i=0; i<sels.length; i++) o += sels[i] * vr.s[i+1]; for (let i=o; i<o+vr.s[sels.length]; i++) { instantiateComponent(vr.cIdx+i); }
function instantiateComponent(cIdx) {
function extractValue(sizes, arr) { if (sizes[0] == 1) return arr[0].v.toString(); const res = []; for (let i=0; i<sizes[0]; i += sizes[1]) { res.push(extractValue(sizes.slice(1), arr.slice(i, i+sizes[1]))); } return res; }
if (ctx.components[cIdx]) return ctx.throwError(fn, "Component already instantiated");
const oldComponent = ctx.currentComponent; const oldFileName = ctx.fileName; const oldFilePath = ctx.filePath; const oldMain = ctx.main; const oldRefs = ctx.refs;
if (ctx.currentComponent==-1) { ctx.main=true; } else { ctx.main=false; }
ctx.currentComponent = cIdx;
ctx.components[cIdx] = { params: {}, names: ctx.newTableName(), nInSignals: 0, template: templateName };
if (template.params.length != paramValues.length) return ctx.throwError(fn, "Invalid number of parameters: " + templateName); ctx.refs = [];
const scope = {}; for (let i=0; i< template.params.length; i++) { ctx.refs.push({ t: "V", s: paramValues[i].s, v: paramValues[i].v, refId: i }); scope[template.params[i]] = i; ctx.components[cIdx].params[template.params[i]] = extractValue(paramValues[i].s, paramValues[i].v); }
createRefs(ctx, template.block, scope);
ctx.fileName = template.fileName; ctx.filePath = template.filePath;
execBlock(ctx, template.block);
ctx.refs = oldRefs; ctx.fileName = oldFileName; ctx.filePath = oldFilePath; ctx.currentComponent = oldComponent; ctx.main = oldMain; } }
function execBlock(ctx, ast) { for (let i=0; i<ast.statements.length; i++) { exec(ctx, ast.statements[i]); if (ctx.returnValue) return; if (ctx.error) return; } }
function clone(a) { const res = { t: a.t, s: a.s }; if (a.t == "V") { res.v = new Array(a.v.length); for (let i=0; i<a.v.length; i++) res.v[i] = a.v[i]; } else if (a.t == "S") { res.sIdx = a.sIdx; } else if (a.t == "C") { res.cIdx = a.cIdx; } return res; }
function execFunctionCall(ctx, ast) {
if (ast.name == "log") { const v = exec(ctx, ast.params[0]); const ev = val(ctx, v, ast); if (ev.v) { console.log(ev.v.toString()); } else { console.log(JSON.stringify(stringifyBigInts(ev))); } return; } if (ast.name == "assert") { const v = exec(ctx, ast.params[0]); const ev = val(ctx, v, ast); if (ctx.F.isZero(ev)) return ctx.throwError(ast, "Assertion failed"); }
const fnc = ctx.functions[ast.name];
if (!fnc) return ctx.throwError("Function not defined");
const paramValues = []; for (let i=0; i< ast.params.length; i++) { const v = exec(ctx, ast.params[i]); if (ctx.error) return;
paramValues.push(v); }
if (ast.params.length != paramValues.length) ctx.throwError(ast, "Invalid Number of parameters");
const oldFileName = ctx.fileName; const oldFilePath = ctx.filePath; const oldRefs = ctx.refs;
ctx.fileName = fnc.fileName; ctx.filePath = fnc.filePath; ctx.refs = [];
const scope = {}; for (let i=0; i< fnc.params.length; i++) { const entry = clone(paramValues[i]); entry.refId = i; scope[fnc.params[i]] = i; ctx.refs.push(entry); }
createRefs(ctx, fnc.block, scope);
execBlock(ctx, fnc.block);
const res = ctx.returnValue; ctx.returnValue = null;
ctx.fileName = oldFileName; ctx.filePath = oldFilePath; ctx.refs = oldRefs;
return res; }
function execReturn(ctx, ast) { ctx.returnValue = exec(ctx, ast.value); return; }
function execVariable(ctx, ast) {
const v = ctx.refs[ast.refId]; if (!v) { return ctx.throwError(ast, "Variable not defined: "+ast.name); }
const sels = []; for (let i=0; i< ast.selectors.length; i++) { const sel = exec(ctx, ast.selectors[i]); if (ctx.error) return; if (sel.s[0] != 1) return ctx.throwError(ast, "Variable selector cannot be an array"); if (sel.v[0].t != "N") return NQVAL; sels.push(Scalar.toNumber(sel.v[0].v)); }
let o = 0; let s = v.s[0];
if (sels.length > v.s.length-2) return ctx.throwError(ast, "Too many selectors"); for (let i=0; i<sels.length; i++) { if (sels[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)); } 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<nOps; i++) { const a = exec(ctx, ast.values[i]); if (ctx.error) return; const aV = val(ctx, a, ast.values[i]); operands.push(aV); }
return { t: "V", s: [1,0], v: [ctx.lc[op](...operands)] }; }
function execOpOp(ctx, ast, op, lr) {
if (ast.values[0].type != "VARIABLE") return ctx.throwError(ast, "incrementing a non variable"); if (!utils.isDefined(ast.values[0].refId)) return ctx.throwError(ast, "Assigning to a non variable"); const left = ctx.refs[ast.values[0].refId]; if (left.t != "V") return ctx.throwError(ast, `Only variables can use ${ast.op} operations`); let leftSels = []; if (ast.values[0].selectors) { for (let i=0; i< ast.values[0].selectors.length; i++) { const sel = exec(ctx, ast.values[0].selectors[i]); if (ctx.error) return;
if (sel.s[0] != 1) return ctx.throwError(ast, "Selector cannot be an array"); if (sel.v[0].t != "N") return {t: "NQ"};
leftSels.push( Scalar.toNumber(sel.v[0].v) ); } } if (!left.s) return ctx.throwError(ast, "variable. not defined yet");
let o = 0; let s = left.s[0]; for (let i=0; i<leftSels.length; i++) { o += leftSels[i]*left.s[i+1]; s = left.s[i+1]; } if (s != 1) return ctx.throwError(ast, `invalid operator ${ast.op} in an array`); const resBefore = left.v[o];
let right; if (ast.values[1]) { const rightRef = exec(ctx, ast.values[1]); if (ctx.error) return; right = val(ctx, rightRef); } else { right = {t:"N", v: ctx.F.one}; }
if (!right) return ctx.throwError(ast, "adding a no number");
const resAfter = ctx.lc[op](resBefore, right); left.v[o] = resAfter;
if (lr == "RIGHT") { return { t: "V", s: [1,0], v: [resBefore] }; } else { return { t: "V", s: [1,0], v: [resAfter] }; } }
// Extract the value of a reference.
// Must be a single value. (cannot be an array)
// The result is an LCAlgebra compatible object {t: ["N"|"LC"|"QEX"|"NQ"], [coefs|v|a,b,c]}
function val(ctx, a, ast) { if (a.s[0] != 1) return ctx.throwError(ast, "Array not allowed"); if (a.t=="V") { return a.v[0]; } else if (a.t=="S") { const res = { t: "LC", coefs: {} }; let sIdx = a.sIdx; while (ctx.signals[sIdx].e >= 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<ast.values.length; i++) { const e = exec(ctx, ast.values[i]); if (i==0) { subSize=e.s; } else { if (!utils.sameSizes(subSize, e.s)) return ctx.throwError(ast, "Array mus be homogeneous"); } if (e.t == "V") { for (let j=0; j<e.v.length;j++) res.v.push(e.v[j]); } else if (e.t == "S") { for (let j=0; j<e.v.length;j++) { const sv = {t: "LC", coefs: {}}; sv.coefs[e.sIdx+j] = ctx.F.one; res.v.push(sv); } } else { return ctx.throwError(ast, "Type not allowed in array"); } }
res.s = [ast.values.length*subSize[0], ...subSize];
return res; }
function createRefs(ctx, ast, scope) { const scopeLabels = []; const scopes = scope ? [scope] : [{}]; iterateAST(ast, (ast, level) => { 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; }
});
}
|