diff --git a/src/c_gen.js b/src/c_gen.js index a886b3b..0da92df 100644 --- a/src/c_gen.js +++ b/src/c_gen.js @@ -87,7 +87,7 @@ function instantiateConstant(ctx, value) { function createRefs(ctx, ast) { const scopeLabels = []; iterateAST(ast, (ast, level) => { - while ((scopeLabels.length>0)&&(!scopeLabels[scopeLabels.length-1].startsWith(level))) { + while ((scopeLabels.length>0)&&(!level.startsWith(scopeLabels[scopeLabels.length-1]))) { ctx.scopes.pop(); scopeLabels.pop(); } @@ -329,7 +329,7 @@ function genDeclareVariable(ctx, ast) { } sizes = utils.accSizes(sizes); } else { - sizes = null; // If not sizes, the sized are defined in the first assignement. + sizes = [1,0]; } if ((!v.sizes)&&(sizes)) { diff --git a/src/compiler.js b/src/compiler.js index 84f9a3b..b1e64f1 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -17,26 +17,18 @@ along with circom. If not, see . */ -const fs = require("fs"); -const path = require("path"); const bigInt = require("big-integer"); const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); const sONE = 0; -const assert = require("assert"); const buildC = require("./c_build"); -const exec = require("./exec"); -const lc = require("./lcalgebra"); +const constructionPhase = require("./construction_phase"); const Ctx = require("./ctx"); -const ZqField = require("./zqfield"); +const ZqField = require("fflib").ZqField; const utils = require("./utils"); const buildR1cs = require("./r1csfile").buildR1cs; module.exports = compile; -const parser = require("../parser/jaz.js").parser; - -const timeout = ms => new Promise(res => setTimeout(res, ms)); - async function compile(srcFile, options) { options.p = options.p || __P__; if (!options) { @@ -45,26 +37,15 @@ async function compile(srcFile, options) { if (typeof options.reduceConstraints === "undefined") { options.reduceConstraints = true; } - const fullFileName = srcFile; - const fullFilePath = path.dirname(fullFileName); - - const src = fs.readFileSync(fullFileName, "utf8"); - const ast = parser.parse(src); - - assert(ast.type == "BLOCK"); - const ctx = new Ctx(); ctx.field = new ZqField(options.p); + ctx.verbose= options.verbose || false; ctx.mainComponent = options.mainComponent || "main"; - ctx.filePath= fullFilePath; - ctx.fileName= fullFileName; - ctx.includedFiles = {}; - ctx.includedFiles[fullFileName] = src.split("\n"); - ctx.verbose= options.verbose || false; + constructionPhase(ctx, srcFile); - exec(ctx, ast); + console.log("NConstraints Before: "+ctx.constraints.length); if (ctx.error) { throw(ctx.error); @@ -91,6 +72,8 @@ async function compile(srcFile, options) { } } + console.log("NConstraints After: "+ctx.constraints.length); + generateWitnessNames(ctx); if (ctx.error) { @@ -235,8 +218,8 @@ function reduceConstants(ctx) { const newConstraints = []; for (let i=0; i. +*/ + +/* + + 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 bigInt = require("big-integer"); + +const LCAlgebra = require("./lcalgebra"); +const parser = require("../parser/jaz.js").parser; + +/* 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.field); + 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: bigInt(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( size.v.toJSNumber() ); + } + + 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( size.v[0].v.toJSNumber() ); + } + + 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("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(sel.v[0].v.toJSNumber()); + } + + 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(sel.v[0].v.toJSNumber()); + } + const sIdx = ctx.components[cIdx].names.getSignalIdx(ast.pin.name, selsP); + + if (sIdx<0) 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 ((!v.v[0].v.isZero())&&(!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 (!v.v[0].v.isZero()) { + 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 (!v.v[0].v.isZero()) { + 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.field.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.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; + } + + + }); + +} + + diff --git a/src/exec.js b/src/exec.js deleted file mode 100644 index 1d67bc5..0000000 --- a/src/exec.js +++ /dev/null @@ -1,1335 +0,0 @@ -/* - 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 . -*/ - -const path = require("path"); -const fs = require("fs"); - -const utils = require("./utils"); - -const bigInt = require("big-integer"); -const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); -const __MASK__ = new bigInt(2).pow(253).minus(1); - -const lc = require("./lcalgebra"); -const parser = require("../parser/jaz.js").parser; - -/* 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 = exec; - - -function exec(ctx, ast) { - if (!ast) { - return error(ctx, ast, "Null AST"); - } - if ((ast.type == "NUMBER") || (ast.type == "LINEARCOMBINATION") || (ast.type =="SIGNAL") || (ast.type == "QEQ")) { - return 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 execVarAssignement(ctx, ast); - } else if (ast.op == "<--") { - return execSignalAssign(ctx, ast); - } else if (ast.op == "<==") { - return execSignalAssignConstrain(ctx, ast); - } else if (ast.op == "===") { - return execConstrain(ctx, ast); - } else if (ast.op == "+=") { - return execVarAddAssignement(ctx, ast); - } else if (ast.op == "*=") { - return execVarMulAssignement(ctx, ast); - } else if (ast.op == "+") { - return execAdd(ctx, ast); - } else if (ast.op == "-") { - return execSub(ctx, ast); - } else if (ast.op == "UMINUS") { - return execUMinus(ctx, ast); - } else if (ast.op == "*") { - return execMul(ctx, ast); - } else if (ast.op == "%") { - return execMod(ctx, ast); - } else if (ast.op == "PLUSPLUSRIGHT") { - return execPlusPlusRight(ctx, ast); - } else if (ast.op == "PLUSPLUSLEFT") { - return execPlusPlusLeft(ctx, ast); - } else if (ast.op == "MINUSMINUSRIGHT") { - return execMinusMinusRight(ctx, ast); - } else if (ast.op == "MINUSMINUSLEFT") { - return execMinusMinusLeft(ctx, ast); - } else if (ast.op == "/") { - return execDiv(ctx, ast); - } else if (ast.op == "\\") { - return execIDiv(ctx, ast); - } else if (ast.op == "**") { - return execExp(ctx, ast); - } else if (ast.op == "&") { - return execBAnd(ctx, ast); - } else if (ast.op == "|") { - return execBOr(ctx, ast); - } else if (ast.op == "^") { - return execBXor(ctx, ast); - } else if (ast.op == "~") { - return execBNot(ctx, ast); - } else if (ast.op == "&&") { - return execAnd(ctx, ast); - } else if (ast.op == "||") { - return execOr(ctx, ast); - } else if (ast.op == "!") { - return execLNot(ctx, ast); - } else if (ast.op == "<<") { - return execShl(ctx, ast); - } else if (ast.op == ">>") { - return execShr(ctx, ast); - } else if (ast.op == "<") { - return execLt(ctx, ast); - } else if (ast.op == ">") { - return execGt(ctx, ast); - } else if (ast.op == "<=") { - return execLte(ctx, ast); - } else if (ast.op == ">=") { - return execGte(ctx, ast); - } else if (ast.op == "==") { - return execEq(ctx, ast); - } else if (ast.op == "!=") { - return execNeq(ctx, ast); - } else if (ast.op == "?") { - return execTerCon(ctx, ast); - } else { - error(ctx, 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 { - error(ctx, 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 ; - } else if (ast.type == "FOR") { - return execFor(ctx, ast); - } else if (ast.type == "WHILE") { - return execWhile(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 { - error(ctx, ast, "Invalid AST node type: " + ast.type); - } -} - -function error(ctx, ast, errStr) { - ctx.error = { - pos: { - first_line: ast.first_line, - first_column: ast.first_column, - last_line: ast.last_line, - last_column: ast.last_column - }, - errStr: errStr, - errFile: ctx.fileName, - ast: ast, - message: errStr - }; -} - -function iterateSelectors(ctx, sizes, baseName, fn) { - if (sizes.length == 0) { - return fn(baseName); - } - const res = []; - for (let i=0; i=0; i--) { - if (ctx.scopes[i][name]) return select(ctx.scopes[i][name].value, sels); - } - return null; - - function reduce(v, _sels, idxName) { - let sels = _sels || []; - let sizes = v.sizes || []; - - let accSizes = [1]; - for (let i=sizes.length-1; i>0; i--) { - accSizes = [accSizes[0]*sizes[i], ...accSizes]; - } - const res = Object.assign({}, v); - res.sizes = sizes.slice(sels.length); - for (let i=0; i=0; i--) { - if (ctx.scopes[i][name]) { - if (ctx.scopes[i][name].type == "COMPONENT") { - return [null, sels, "COMPONENT"]; - } else { - return select(ctx.scopes[i][name].value, sels, ctx.scopes[i][name].type); - } - } - } - return [null, [], ""]; -} - - -function setScopeRef(ctx, name, sels, value) { - let l = getScopeLevel(ctx, name); - if (l==-1) l= ctx.scopes.length-1; - - if (sels.length == 0) { - ctx.scopes[l][name].value = value; - } else { - setScopeArray(ctx.scopes[l][name].value, sels); - } - - function setScopeArray(a, sels) { - if (sels.length == 1) { - a[sels[0]] = value; - } else { - setScopeArray(a[sels[0]], sels.slice(1)); - } - } -} - -function getScopeLevel(ctx, name) { - for (let i=ctx.scopes.length-1; i>=0; i--) { - if (ctx.scopes[i][name]) return i; - } - return -1; -} - -function execBlock(ctx, ast) { - for (let i=0; i= 0 )&&(ast.op != "=")) return error(ctx, ast, `Cannot assign to a var with ${ast.op}. use = op`); - - const res = exec(ctx, ast.values[1]); - if (ctx.error) return; - - setScopeRef(ctx, v.name, sels, res); - - return v; -} - -function execLt(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.lt(b.value) ? bigInt(1) : bigInt(0) - }; -} - -function execGt(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.gt(b.value) ? bigInt(1) : bigInt(0) - }; -} - -function execLte(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.lesserOrEquals(b.value) ? bigInt(1) : bigInt(0) - }; -} - -function execGte(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.greaterOrEquals(b.value) ? bigInt(1) : bigInt(0) - }; -} - - -function execEq(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.eq(b.value) ? bigInt(1) : bigInt(0) - }; -} - -function execNeq(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.eq(b.value) ? bigInt(0) : bigInt(1) - }; -} - -function execBAnd(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.and(b.value).and(__MASK__) - }; -} - -function execBOr(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.or(b.value).and(__MASK__) - }; -} - -function execBXor(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.xor(b.value).and(__MASK__) - }; -} - -function execBNot(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value) return { type: "NUMBER" }; - - const res = lc.negate(a); - if (res.type == "ERROR") return error(ctx, ast, res.errStr); - - return { - type: "NUMBER", - value: a.value.xor(__MASK__).and(__MASK__) - }; -} - - - -function execAnd(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: (a.value.neq(0) && b.value.neq(0)) ? bigInt(1) : bigInt(0) - }; -} - -function execOr(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: (a.value.neq(0) || b.value.neq(0)) ? bigInt(1) : bigInt(0) - }; -} - -function execLNot(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: (a.value.eq(0)) ? bigInt(1) : bigInt(0) - }; -} - - -function execShl(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - const v = b.value.greater(256) ? 256 : b.value.value; - return { - type: "NUMBER", - value: a.value.shiftLeft(v).and(__MASK__) - }; -} - -function execShr(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - const v = b.value.greater(256) ? 256 : b.value.value; - return { - type: "NUMBER", - value: a.value.shiftRight(v).and(__MASK__) - }; -} - -function execMod(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.mod(b.value) - }; -} - - -function execExp(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - return { - type: "NUMBER", - value: a.value.modPow(b.value, __P__) - }; -} - -function execDiv(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - if (b.value.isZero()) return error(ctx, ast, "Division by zero"); - return { - type: "NUMBER", - value: a.value.times(b.value.modInv(__P__)).mod(__P__) - }; -} - -function execIDiv(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - if (a.type != "NUMBER") return { type: "NUMBER" }; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - if (b.type != "NUMBER") return { type: "NUMBER" }; - if (!a.value || !b.value) return { type: "NUMBER" }; - if (b.value.isZero()) return error(ctx, ast, "Division by zero"); - return { - type: "NUMBER", - value: a.value.divide(b.value) - }; -} - -function execAdd(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - - const res = lc.add(a,b); - if (res.type == "ERROR") return error(ctx, ast, res.errStr); - - return res; -} - -function execSub(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - - const res = lc.sub(a,b); - if (res.type == "ERROR") return error(ctx, ast, res.errStr); - - return res; -} - -function execUMinus(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - - const res = lc.negate(a); - if (res.type == "ERROR") return error(ctx, ast, res.errStr); - - return res; -} - -function execMul(ctx, ast) { - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - - const res = lc.mul(a,b); - if (res.type == "ERROR") return error(ctx, ast, res.errStr); - - return res; -} - - -function execVarAddAssignement(ctx, ast) { - const res = execAdd(ctx,{ values: [ast.values[0], ast.values[1]] } ); - if (ctx.error) return; - return execVarAssignement(ctx, { op:"=", values: [ast.values[0], res] }); -} - -function execVarMulAssignement(ctx, ast) { - const res = execMul(ctx,{ values: [ast.values[0], ast.values[1]] } ); - if (ctx.error) return; - return execVarAssignement(ctx, { op:"=", values: [ast.values[0], res] }); -} - -function execPlusPlusRight(ctx, ast) { - const resBefore = exec(ctx, ast.values[0]); - if (ctx.error) return; - const resAfter = execAdd(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); - if (ctx.error) return; - execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); - return resBefore; -} - -function execPlusPlusLeft(ctx, ast) { - if (ctx.error) return; - const resAfter = execAdd(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); - if (ctx.error) return; - execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); - return resAfter; -} - -function execMinusMinusRight(ctx, ast) { - const resBefore = exec(ctx, ast.values[0]); - if (ctx.error) return; - const resAfter = execSub(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); - if (ctx.error) return; - execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); - return resBefore; -} - -function execMinusMinusLeft(ctx, ast) { - if (ctx.error) return; - const resAfter = execSub(ctx,{ values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}] } ); - if (ctx.error) return; - execVarAssignement(ctx, { op:"=", values: [ast.values[0], resAfter] }); - return resAfter; -} - -function execTerCon(ctx, ast) { - const cond = exec(ctx, ast.values[0]); - if (ctx.error) return; - - if (!cond.value) return { type: "NUMBER" }; - - if (cond.value.neq(0)) { - return exec(ctx, ast.values[1]); - } else { - return exec(ctx, ast.values[2]); - } -} - -function execSignalAssign(ctx, ast) { - let vDest; - if (ast.values[0].type == "DECLARE") { - vDest = exec(ctx, ast.values[0]); - if (ctx.error) return; - } else { - vDest = ast.values[0]; - } - - let dst; - if (vDest.type == "VARIABLE") { - dst = getScope(ctx, vDest.name, vDest.selectors); - if (ctx.error) return; - } else if (vDest.type == "PIN") { - dst = execPin(ctx, vDest); - if (ctx.error) return; - } else { - error(ctx, ast, "Bad assignement"); - } - - if (!dst) return error(ctx, ast, "Signal not defined"); - if (dst.type != "SIGNAL") return error(ctx, ast, "Signal assigned to a non signal"); - - let sDest=ctx.signals[dst.sIdx]; - if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.sIdx); - - 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 (sDest.value) return error(ctx, ast, "Signals cannot be assigned twice"); - - let src = exec(ctx, ast.values[1]); - if (ctx.error) return; - - - /* - let vSrc; - if (ast.values[1].type == "DECLARE") { - vSrc = exec(ctx, ast.values[1]); - if (ctx.error) return; - } else { - vSrc = ast.values[1]; - } - - if (vSrc.type == "VARIABLE") { - src = getScope(ctx, vSrc.name, vSrc.selectors); - if (!src) error(ctx, ast, "Variable not defined: " + vSrc.name); - if (ctx.error) return; - } else if (vSrc.type == "PIN") { - src = execPin(ctx, vSrc); - } - */ - - let assignValue = true; - if (src.type == "SIGNAL") { - let sSrc = ctx.signals[src.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 = src.sIdx; - while (sDest.e >= 0) sDest=ctx.signals[sDest.e]; - assignValue = false; - } - } - - if (assignValue) { - // const resLC = exec(ctx, vSrc); - if (ctx.error) return; - - // const v = lc.evaluate(ctx, resLC); - const v = lc.evaluate(ctx, src); - - if (v.value) { - sDest.v = v.value; - } - } - - return vDest; -} - -function execConstrain(ctx, ast) { - ast.fileName = ctx.fileName; - ast.filePath = ctx.filePath; - const a = exec(ctx, ast.values[0]); - if (ctx.error) return; - const b = exec(ctx, ast.values[1]); - if (ctx.error) return; - - const res = lc.sub(a,b); - if (res.type == "ERROR") return error(ctx, ast, res.errStr); - - if (!lc.isZero(res)) { - ctx.constraints.push(lc.toQEQ(res)); - if ((ctx.constraints.length % 10000 == 0)&&(ctx.constraints.length>0)) console.log("Constraints: " + ctx.constraints.length); - } - - return res; -} - -function execSignalAssignConstrain(ctx, ast) { - const v = execSignalAssign(ctx,ast); - if (ctx.error) return; - execConstrain(ctx, ast); - if (ctx.error) return; - return v; -} - -function execInclude(ctx, ast) { - const incFileName = path.resolve(ctx.filePath, ast.file); - const incFilePath = path.dirname(incFileName); - - ctx.includedFiles = ctx.includedFiles || []; - if (ctx.includedFiles[incFileName]) return; - - - const src = fs.readFileSync(incFileName, "utf8"); - - ctx.includedFiles[incFileName] = src.split("\n"); - - if (!src) return error(ctx, ast, "Include file not found: "+incFileName); - - const incAst = parser.parse(src); - - const oldFilePath = ctx.filePath; - const oldFileName = ctx.fileName; - ctx.filePath = incFilePath; - ctx.fileName = incFileName; - - exec(ctx, incAst); - - ast.block = incAst; - - ctx.filePath = oldFilePath; - ctx.fileName = oldFileName; -} - -function execArray(ctx, ast) { - const res = []; - - for (let i=0; i Invalid AST iteration: " + ast.type); } diff --git a/src/lcalgebra.js b/src/lcalgebra.js index 4419931..5ed8c43 100644 --- a/src/lcalgebra.js +++ b/src/lcalgebra.js @@ -18,491 +18,555 @@ */ /* - NUMBER: a + // Number + /////////////// + N: a { - type: "NUMBER", - value: bigInt(a) + t: "N", + v: bigInt(a) } - LINEARCOMBINATION: c1*s1 + c2*s2 + c3*s3 + // Signal + /////////////// + { + t: "S", + sIdx: sIdx + } + // Linear Convination + ////////////////// + LC: c1*s1 + c2*s2 + c3*s3 { - type: "LINEARCOMBINATION", - values: { + t: "LC", + coefs: { s1: bigInt(c1), s2: bigInt(c2), s3: bigInt(c3) } } + // Quadratic Expression + ////////////////// + QEX: a*b + c WHERE a,b,c are LC + { + t: "QEX" + a: { t: "LC", coefs: {...} }, + b: { t: "LC", coefs: {...} }, + c: { t: "LC", coefs: {...} } + } - QEQ: a*b + c WHERE a,b,c are LINEARCOMBINATION + NQ: Non quadratic expression { - type: "QEQ" - a: { type: LINEARCOMBINATION, values: {...} }, - b: { type: LINEARCOMBINATION, values: {...} }, - c: { type: LINEARCOMBINATION, values: {...} } + t: "NQ" } */ /* -+ NUM LC QEQ -NUM NUM LC QEQ -LC LC LC QEQ -QEQ QEQ QEQ ERR - -* NUM LC QEQ -NUM NUM LC QEQ -LC LC QEQ ERR -QEQ QEQ ERR ERR ++ N LC QEX NQ +N N LC QEX NQ +LC LC LC QEX NQ +QEX QEX QEX NQ NQ +NQ NQ NQ NQ NQ + +* N LC QEX NQ +N N LC QEX NQ +LC LC QEX NQ NQ +QEX QEX NQ NQ NQ +NQ NQ NQ NQ NQ */ const bigInt = require("big-integer"); -const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617"); +const utils = require("./utils"); const sONE = 0; -const utils = require("./utils.js"); - -exports.add = add; -exports.mul = mul; -exports.evaluate = evaluate; -exports.negate = negate; -exports.sub = sub; -exports.toQEQ = toQEQ; -exports.isZero = isZero; -exports.toString = toString; -exports.canonize = canonize; -exports.substitute = substitute; - -function signal2lc(a) { - let lc; - if (a.type == "SIGNAL") { - lc = { - type: "LINEARCOMBINATION", - values: {} - }; - lc.values[a.sIdx] = bigInt(1); - return lc; - } else { - return a; + +class LCAlgebra { + constructor (aField) { + const self = this; + this.field= aField; + [ + ["lt",2], + ["leq",2], + ["eq",2], + ["neq",2], + ["geq",2], + ["gt",2], + ["idiv",2], + ["mod",2], + ["band",2], + ["bor",2], + ["bxor",2], + ["bnot",2], + ["land",2], + ["lor",2], + ["lnot",2], + ["shl",2], + ["shr",2], + ].forEach( (op) => { + self._genNQOp(op[0], op[1]); + }); } -} -function clone(a) { - const res = {}; - res.type = a.type; - if (a.type == "NUMBER") { - res.value = bigInt(a.value); - } else if (a.type == "LINEARCOMBINATION") { - res.values = {}; - for (let k in a.values) { - res.values[k] = bigInt(a.values[k]); - } - } else if (a.type == "QEQ") { - res.a = clone(a.a); - res.b = clone(a.b); - res.c = clone(a.c); - } else if (a.type == "ERROR") { - res.errStr = a.errStr; - } else { - res.type = "ERROR"; - res.errStr = "Invilid type when clonning: "+a.type; + _genNQOp(op, nOps) { + const self=this; + self[op] = function() { + const operands = []; + for (let i=0; i= 0) { - return getSignalValue(ctx, s.e); - } else { - const res = { - type: "NUMBER" - }; - if (s.v) { - res.value = s.v; + neg(_a) { + const a = this._signal2lc(_a); + let res = this._clone(a); + if (res.t == "N") { + res.v = this.field.neg(a.v); + } else if (res.t == "LC") { + for (let k in res.coefs) { + res.coefs[k] = this.field.neg(res.coefs[k]); + } + } else if (res.t == "QEX") { + res.a = this.neg(res.a); + res.c = this.neg(res.c); + } else { + res = {t: "NQ"}; } return res; } -} -function evaluate(ctx, n) { - if (n.type == "NUMBER") { - return n; - } else if (n.type == "SIGNAL") { - return getSignalValue(ctx, n.sIdx); - } else if (n.type == "LINEARCOMBINATION") { - const v= { - type: "NUMBER", - value: bigInt(0) - }; - for (let k in n.values) { - const s = getSignalValue(ctx, k); - if (s.type != "NUMBER") return {type: "ERROR", errStr: "Invalid signal in linear Combination: " + k}; - if (!s.value) return { type: "NUMBER" }; - v.value = v.value.add( n.values[k].times(s.value)).mod(__P__); + sub(a, b) { + return this.add(a, this.neg(b)); + } + + div(a, b) { + if (b.t == "N") { + if (b.v.isZero()) throw new Error("Division by zero"); + const inv = { + t: "N", + v: this.field.inv(b.v) + }; + return this.mul(a, inv); + } else { + return {t: "NQ"}; } - return v; - } else if (n.type == "QEQ") { - const a = evaluate(ctx, n.a); - if (a.type == "ERROR") return a; - if (!a.value) return { type: "NUMBER" }; - const b = evaluate(ctx, n.b); - if (b.type == "ERROR") return b; - if (!b.value) return { type: "NUMBER" }; - const c = evaluate(ctx, n.c); - if (c.type == "ERROR") return c; - if (!c.value) return { type: "NUMBER" }; - - return { - type: "NUMBER", - value: (a.value.times(b.value).add(c.value)).mod(__P__) - }; - } else if (n.type == "ERROR") { - return n; - } else { - return {type: "ERROR", errStr: "Invalid type in evaluate: "+n.type}; } -} -function negate(_a) { - const a = signal2lc(_a); - let res = clone(a); - if (res.type == "NUMBER") { - res.value = __P__.minus(a.value).mod(__P__); - } else if (res.type == "LINEARCOMBINATION") { - for (let k in res.values) { - res.values[k] = __P__.minus(res.values[k]).mod(__P__); + pow(a, b) { + if (b.t == "N") { + if (b.v.isZero()) { + if (this.isZero(a)) { + throw new Error("Zero to the Zero"); + } + return { + t: "N", + v: this.field.one + }; + } else if (b.v.eq(this.field.one)) { + return a; + } else if (b.v.eq(bigInt(2))) { + return this.mul(a,a); + } else { + if (a.t=="N") { + return { + t: "N", + v: this.field.pow(a.v, b.v) + }; + } else { + return {t: "NQ"}; + } + } + } else { + return {t: "NQ"}; } - } else if (res.type == "QEQ") { - res.a = negate(res.a); - res.c = negate(res.c); - } else if (res.type == "ERROR") { - return res; - } else { - res = {type: "ERROR", errStr: "LC Negate invalid Type: "+res.type}; } - return res; -} -function sub(a, b) { - return add(a, negate(b)); -} + substitute(where, signal, equivalence) { + if (equivalence.t != "LC") throw new Error("Equivalence must be a Linear Combination"); + if (where.t == "LC") { + if (!utils.isDefined(where.coefs[signal]) || where.coefs[signal].isZero()) return where; + const res=this._clone(where); + const coef = res.coefs[signal]; + for (let k in equivalence.coefs) { + if (k != signal) { + const v = this.field.mul( coef, equivalence.coefs[k] ); + if (!utils.isDefined(res.coefs[k])) { + res.coefs[k]=v; + } else { + res.coefs[k]= this.field.add(res.coefs[k],v); + } + if (res.coefs[k].isZero()) delete res.coefs[k]; + } + } + delete res.coefs[signal]; + return res; + } else if (where.t == "QEX") { + const res = { + t: "QEX", + a: this.substitute(where.a, signal, equivalence), + b: this.substitute(where.b, signal, equivalence), + c: this.substitute(where.c, signal, equivalence) + }; + return res; + } else { + return where; + } + } -function toQEQ(a) { - if (a.type == "NUMBER") { - return { - type: "QEQ", - a: {type: "LINEARCOMBINATION", values: {}}, - b: {type: "LINEARCOMBINATION", values: {}}, - c: {type: "LINEARCOMBINATION", values: {sONE: bigInt(a.value)}} - }; - } else if (a.type == "LINEARCOMBINATION") { - return { - type: "QEQ", - a: {type: "LINEARCOMBINATION", values: {}}, - b: {type: "LINEARCOMBINATION", values: {}}, - c: clone(a) - }; - } else if (a.type == "QEQ") { - return clone(a); - } else if (a.type == "ERROR") { - return clone(a); - } else { - return {type: "ERROR", errStr: "toQEQ invalid Type: "+a.type}; + toQEX(a) { + if (a.t == "N") { + const res = { + t: "QEX", + a: {t: "LC", coefs: {}}, + b: {t: "LC", coefs: {}}, + c: {t: "LC", coefs: {}} + }; + res.c[sONE] = a.v; + return res; + } else if (a.t == "LC") { + return { + t: "QEX", + a: {t: "LC", coefs: {}}, + b: {t: "LC", coefs: {}}, + c: this._clone(a) + }; + } else if (a.t == "QEX") { + return this._clone(a); + } else { + throw new Error(`Type ${a.t} can not be converted to QEX`); + } } -} -function isZero(a) { - if (a.type == "NUMBER") { - return a.value.isZero(); - } else if (a.type == "LINEARCOMBINATION") { - for (let k in a.values) { - if (!a.values[k].isZero()) return false; + isZero(a) { + if (a.t == "N") { + return a.v.isZero(); + } else if (a.t == "LC") { + for (let k in a.coefs) { + if (!a.coefs[k].isZero()) return false; + } + return true; + } else if (a.t == "QEX") { + return (this.isZero(a.a) || this.isZero(a.b)) && this.isZero(a.c); + } else { + return false; } - return true; - } else if (a.type == "QEQ") { - return (isZero(a.a) || isZero(a.b)) && isZero(a.c); - } else if (a.type == "ERROR") { - return false; - } else { - return false; } -} -function toString(a, ctx) { - if (a.type == "NUMBER") { - return a.value.toString(); - } else if (a.type == "LINEARCOMBINATION") { - let S=""; - for (let k in a.values) { - if (!a.values[k].isZero()) { - let c; - if (a.values[k].greater(__P__.divide(2))) { - S = S + "-"; - c = __P__.minus(a.values[k]); - } else { - if (S!="") S=S+" + "; - c = a.values[k]; + toString(a, ctx) { + if (a.t == "N") { + return a.v.toString(); + } else if (a.t == "LC") { + let S=""; + for (let k in a.coefs) { + if (!a.coefs[k].isZero()) { + let c; + if (a.coefs[k].greater(this.field.p.divide(2))) { + S = S + "-"; + c = this.field.p.minus(a.coefs[k]); + } else { + if (S!="") S=S+" + "; + c = a.coefs[k]; + } + if (!c.equals(bigInt.one)) { + S = S + c.toString() + "*"; + } + let sIdx = k; + if (ctx) { + while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e; + } + S = S + "[" + sIdx + "]"; } - if (!c.equals(1)) { - S = S + c.toString() + "*"; - } - let sIdx = k; - if (ctx) { - while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e; - } - S = S + "[" + sIdx + "]"; } + if (S=="") return "0"; else return S; + } else if (a.t == "QEX") { + return "( "+ + this.toString(a.a, ctx)+" ) * ( "+ + this.toString(a.b, ctx)+" ) + " + + this.toString(a.c, ctx); + } else { + return "NQ"; } - if (S=="") return "0"; else return S; - } else if (a.type == "QEQ") { - return "( "+toString(a.a, ctx)+" ) * ( "+toString(a.b, ctx)+" ) + " + toString(a.c, ctx); - } else if (a.type == "ERROR") { - return "ERROR: "+a.errStr; - } else { - return "INVALID"; } -} -function canonize(ctx, a) { - if (a.type == "LINEARCOMBINATION") { - const res = clone(a); - for (let k in a.values) { - let s = k; - while (ctx.signals[s].e>=0) s= ctx.signals[s].e; - if (utils.isDefined(ctx.signals[s].v)&&(k != sONE)) { - const v = res.values[k].times(ctx.signals[s].v).mod(__P__); - if (!res.values[sONE]) { - res.values[sONE]=v; - } else { - res.values[sONE]= res.values[sONE].add(v).mod(__P__); - } - delete res.values[k]; - } else if (s != k) { - if (!res.values[s]) { - res.values[s]=bigInt(res.values[k]); - } else { - res.values[s]= res.values[s].add(res.values[k]).mod(__P__); - } - delete res.values[k]; + evaluate(ctx, n) { + if (n.t == "N") { + return n.v; + } else if (n.t == "SIGNAL") { + return getSignalValue(ctx, n.sIdx); + } else if (n.t == "LC") { + let v= this.field.zero; + for (let k in n.coefs) { + const s = getSignalValue(ctx, k); + if (s === null) return null; + v = this.field.add(v, this.field.mul( n.coefs[k], s)); } + return v; + } else if (n.type == "QEX") { + const a = this.evaluate(ctx, n.a); + if (a === null) return null; + const b = this.evaluate(ctx, n.b); + if (b === null) return null; + const c = this.evaluate(ctx, n.c); + if (c === null) return null; + + return this.field.add(this.field.mul(a,b), c); + } else { + return null; } - for (let k in res.values) { - if (res.values[k].isZero()) delete res.values[k]; + + + function getSignalValue(ctx, sIdx) { + let s = ctx.signals[sIdx]; + while (s.e>=0) s = ctx.signals[s.e]; + if (utils.isDefined(s.v)) return s.v; + return null; } - return res; - } else if (a.type == "QEQ") { - const res = { - type: "QEQ", - a: canonize(ctx, a.a), - b: canonize(ctx, a.b), - c: canonize(ctx, a.c) - }; - return res; - } else { - return a; + } -} -function substitute(where, signal, equivalence) { - if (equivalence.type != "LINEARCOMBINATION") throw new Error("Equivalence must be a Linear Combination"); - if (where.type == "LINEARCOMBINATION") { - if (!where.values[signal] || where.values[signal].isZero()) return where; - const res=clone(where); - const coef = res.values[signal]; - for (let k in equivalence.values) { - if (k != signal) { - const v = coef.times(equivalence.values[k]).mod(__P__); - if (!res.values[k]) { - res.values[k]=v; - } else { - res.values[k]= res.values[k].add(v).mod(__P__); + + canonize(ctx, a) { + if (a.t == "LC") { + const res = this._clone(a); + for (let k in a.coefs) { + let s = k; + while (ctx.signals[s].e>=0) s= ctx.signals[s].e; + if (utils.isDefined(ctx.signals[s].v)&&(k != sONE)) { + const v = this.field.mul(res.coefs[k], ctx.signals[s].v); + if (!utils.isDefined(res.coefs[sONE])) { + res.coefs[sONE]=v; + } else { + res.coefs[sONE]= this.field.add(res.coefs[sONE], v); + } + delete res.coefs[k]; + } else if (s != k) { + if (!utils.isDefined(res.coefs[s])) { + res.coefs[s]=res.coefs[k]; + } else { + res.coefs[s]= this.field.add(res.coefs[s], res.coefs[k]); + } + delete res.coefs[k]; } - if (res.values[k].isZero()) delete res.values[k]; } + for (let k in res.coefs) { + if (res.coefs[k].isZero()) delete res.coefs[k]; + } + return res; + } else if (a.t == "QEX") { + const res = { + t: "QEX", + a: this.canonize(ctx, a.a), + b: this.canonize(ctx, a.b), + c: this.canonize(ctx, a.c) + }; + return res; + } else { + return a; } - delete res.values[signal]; - return res; - } else if (where.type == "QEQ") { - const res = { - type: "QEQ", - a: substitute(where.a, signal, equivalence), - b: substitute(where.b, signal, equivalence), - c: substitute(where.c, signal, equivalence) - }; - return res; - } else { - return where; } } +module.exports = LCAlgebra; + + + + + + diff --git a/src/r1csfile.js b/src/r1csfile.js index 870adea..b81d620 100644 --- a/src/r1csfile.js +++ b/src/r1csfile.js @@ -1,7 +1,6 @@ const fs = require("fs"); const assert = require("assert"); -const lc = require("./lcalgebra"); const bigInt = require("big-integer"); module.exports.buildR1cs = buildR1cs; @@ -262,19 +261,19 @@ async function buildR1cs(ctx, fileName) { async function writeConstraint(c) { await writeLC(c.a); await writeLC(c.b); - await writeLC(lc.negate(c.c)); + await writeLC(ctx.lc.neg(c.c)); } async function writeLC(lc) { - const idxs = Object.keys(lc.values); + const idxs = Object.keys(lc.coefs); await writeU32(idxs.length); - for (let s in lc.values) { + for (let s in lc.coefs) { let lSignal = ctx.signals[s]; while (lSignal.e >=0 ) lSignal = ctx.signals[lSignal.e]; await writeU32(lSignal.id); - await writeBigInt(lc.values[s]); + await writeBigInt(lc.coefs[s]); } } diff --git a/src/utils.js b/src/utils.js index e790c32..a6bf11b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -124,3 +124,4 @@ function accSizes2Str(sizes) { + diff --git a/src/zqfield.js b/src/zqfield.js deleted file mode 100644 index d0fef73..0000000 --- a/src/zqfield.js +++ /dev/null @@ -1,118 +0,0 @@ -const bigInt = require("big-integer"); -const assert = require("assert"); - -module.exports = class ZqField { - constructor(p) { - this.one = bigInt.one; - this.zero = bigInt.zero; - this.p = p; - this.bitLength = p.bitLength(); - this.mask = bigInt.one.shiftLeft(this.bitLength - 1).minus(bigInt.one); - } - - add(a, b) { - let res = a.add(b); - if (res.geq(this.p)) { - res = res.minus(this.p); - } - return res; - } - - sub(a, b) { - if (a.geq(b)) { - return a.minus(b); - } else { - return this.p.minus(b.minus(a)); - } - } - - neg(a) { - if (a.isZero()) return a; - return this.p.minus(a); - } - - mul(a, b) { - return a.times(b).mod(this.p); - } - - lt(a, b) { - return a.lt(b) ? bigInt(1) : bigInt(0); - } - - eq(a, b) { - return a.eq(b) ? bigInt(1) : bigInt(0); - } - - gt(a, b) { - return a.gt(b) ? bigInt(1) : bigInt(0); - } - - leq(a, b) { - return a.leq(b) ? bigInt(1) : bigInt(0); - } - - geq(a, b) { - return a.geq(b) ? bigInt(1) : bigInt(0); - } - - neq(a, b) { - return a.neq(b) ? bigInt(1) : bigInt(0); - } - - div(a, b) { - assert(!b.isZero(), "Division by zero"); - return a.times(b.modInv(this.p)).mod(this.p); - } - - idiv(a, b) { - assert(!b.isZero(), "Division by zero"); - return a.divide(b); - } - - mod(a, b) { - return a.mod(b); - } - - pow(a, b) { - return a.modPow(b, this.p); - } - - band(a, b) { - return a.and(b).and(this.mask); - } - - bor(a, b) { - return a.or(b).and(this.mask); - } - - bxor(a, b) { - return a.xor(b).and(this.mask); - } - - bnot(a) { - return a.xor(this.mask).and(this.mask); - } - - shl(a, b) { - if (b.geq(this.bitLength)) return bigInt.zero; - return a.shiftLeft(b).and(this.mask); - } - - shr(a, b) { - if (b.geq(this.bitLength)) return bigInt.zero; - return a.shiftRight(b).and(this.mask); - } - - land(a, b) { - return (a.isZero() || b.isZero) ? bigInt.zero : bigInt.one; - } - - lor(a, b) { - return (a.isZero() && b.isZero) ? bigInt.zero : bigInt.one; - } - - lnot(a) { - return a.isZero() ? bigInt.one : bigInt.zero; - } -}; - diff --git a/test/basiccases.js b/test/basiccases.js index 1b45f82..b5619e7 100644 --- a/test/basiccases.js +++ b/test/basiccases.js @@ -44,7 +44,8 @@ async function doTest(circuit, testVectors) { describe("basic cases", function () { this.timeout(100000); -/* it("inout", async () => { +/* + it("inout", async () => { await doTest( "inout.circom", [ @@ -298,6 +299,17 @@ describe("basic cases", function () { ] ); }); +*/ + it("Component array 2d", async () => { + await doTest( + "componentarray2.circom", + [ + [{in: [1,2]}, {out: [1, 256]}], + [{in: [0,3]}, {out: [0, 6561]}], + ] + ); + }); +/* it("Constant circuit", async () => { await doTest( "constantcircuit.circom", @@ -306,12 +318,11 @@ describe("basic cases", function () { [{}, {out: [1,0,1,0, 0,0,0,1, 0,1,1,1, 0,1,0,1, 1,1,1,0, 0,1,1,0, 1,1,0,1, 1,1,0,1]}], ] ); - }); */ + }); it("Constant internal circuit", async () => { await doTest( "constantinternalcircuit.circom", [ - // 0xbb67ae85 [{in: 1}, {out: 5}], [{in: 0}, {out: 4}], [{in: -2}, {out: 2}], @@ -319,4 +330,14 @@ describe("basic cases", function () { ] ); }); + it("include", async () => { + await doTest( + "include.circom", + [ + [{in: 3}, {out: 6}], + [{in: 6}, {out: 15}], + ] + ); + }); +*/ }); diff --git a/test/circuits/arrays.circom b/test/circuits/arrays.circom index 17df863..30ca5ce 100644 --- a/test/circuits/arrays.circom +++ b/test/circuits/arrays.circom @@ -4,18 +4,15 @@ function Add3(arr1, arr2, arr3) { var res[3]; - var i; - var j; - res[0] = arr1; res[1] = 0; - for (i=0; i<2; i += 1) { + for (var i=0; i<2; i += 1) { res[1] = res[1] + arr2[i]; } res[2] = 0; - for (i=0; i<2; i++) { - for (j=0; j<3; j += 1) { + for (var i=0; i<2; i++) { + for (var j=0; j<3; j += 1) { res[2] = res[2] + arr3[i][j]; } } @@ -27,8 +24,8 @@ template Main() { signal input in; signal output out[3]; - var c = Add3(1, [2,3], [[4,5,6], [7,8,9]]); // [1, 5, 39]; - var d = Add3(in, [in+1, in+2], [[in+1, in+2, in+3], [in+1, in+2, in+3]]); + var c[3] = Add3(1, [2,3], [[4,5,6], [7,8,9]]); // [1, 5, 39]; + var d[3] = Add3(in, [in+1, in+2], [[in+1, in+2, in+3], [in+1, in+2, in+3]]); out[0] <-- d[0] + c[0]; out[0] === in+c[0]; diff --git a/test/circuits/componentarray2.circom b/test/circuits/componentarray2.circom new file mode 100644 index 0000000..7b3669e --- /dev/null +++ b/test/circuits/componentarray2.circom @@ -0,0 +1,27 @@ +template Square() { + signal input in; + signal output out; + + out <== in**2; +} + +template Main(n, nrounds) { + signal input in[n]; + signal output out[n]; + + component squares[n][nrounds]; + + for (var i=0; i out[i]; + } +} + +component main = Main(2, 3); diff --git a/test/circuits/constantcircuit.circom b/test/circuits/constantcircuit.circom index 6cff5dd..8fec25c 100644 --- a/test/circuits/constantcircuit.circom +++ b/test/circuits/constantcircuit.circom @@ -1,6 +1,6 @@ template H(x) { signal output out[32]; - var c = [0x6a09e667, + var c[8] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, diff --git a/test/circuits/include.circom b/test/circuits/include.circom new file mode 100644 index 0000000..2d25388 --- /dev/null +++ b/test/circuits/include.circom @@ -0,0 +1,16 @@ +include "included.circom"; +include "included.circom"; // Include twice is fine. The second one is not included + +template Main() { + signal input in; + signal output out; + + component t1 = T1(); + + var a = F1(3); + + in ==> t1.in; + t1.out + a ==> out; /// out <-- in**2/3+3 +} + +component main = Main(); diff --git a/test/circuits/included.circom b/test/circuits/included.circom new file mode 100644 index 0000000..70e7dde --- /dev/null +++ b/test/circuits/included.circom @@ -0,0 +1,10 @@ +template T1() { + signal input in; + signal output out; + + out <== in**2/3; +} + +function F1(a) { + return a**2/3; +}