/*
|
|
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/>.
|
|
*/
|
|
|
|
const path = require("path");
|
|
const fs = require("fs");
|
|
|
|
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 execAnd(ctx, ast);
|
|
} else if (ast.op == "||") {
|
|
return execOr(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<sizes[0]; i++) {
|
|
res.push(iterateSelectors(ctx, sizes.slice(1), baseName+"["+i+"]", fn));
|
|
if (ctx.error) return null;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function setScope(ctx, name, selectors, value) {
|
|
let l = getScopeLevel(ctx, name);
|
|
if (l==-1) l= ctx.scopes.length-1;
|
|
|
|
if (selectors.length == 0) {
|
|
ctx.scopes[l][name] = value;
|
|
} else {
|
|
setScopeArray(ctx.scopes[l][name], selectors);
|
|
}
|
|
|
|
function setScopeArray(a, sels) {
|
|
if (sels.length == 1) {
|
|
a[sels[0].value] = value;
|
|
} else {
|
|
setScopeArray(a[sels[0]], sels.slice(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getScope(ctx, name, selectors) {
|
|
|
|
const sels = [];
|
|
if (selectors) {
|
|
for (let i=0; i< selectors.length; i++) {
|
|
const idx = exec(ctx, selectors[i]);
|
|
if (ctx.error) return;
|
|
|
|
if (idx.type != "NUMBER") return error(ctx, selectors[i], "expected a number");
|
|
sels.push( idx.value.toJSNumber() );
|
|
}
|
|
}
|
|
|
|
|
|
function select(v, s) {
|
|
s = s || [];
|
|
if (s.length == 0) return v;
|
|
return select(v[s[0]], s.slice(1));
|
|
}
|
|
|
|
for (let i=ctx.scopes.length-1; i>=0; i--) {
|
|
if (ctx.scopes[i][name]) return select(ctx.scopes[i][name], sels);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
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<ast.statements.length; i++) {
|
|
exec(ctx, ast.statements[i]);
|
|
if (ctx.returnValue) return;
|
|
if (ctx.error) return;
|
|
}
|
|
}
|
|
|
|
function execTemplateDef(ctx, ast) {
|
|
const scope = ctx.scopes[0]; // Lets put templates always in top scope.
|
|
// const scope = ctx.scopes[ctx.scopes.length-1];
|
|
if (getScope(ctx, ast.name)) {
|
|
return error(ctx, ast, "Name already exists: "+ast.name);
|
|
}
|
|
scope[ast.name] = {
|
|
type: "TEMPLATE",
|
|
params: ast.params,
|
|
block: ast.block,
|
|
fileName: ctx.fileName,
|
|
filePath: ctx.filePath,
|
|
scopes: copyScope(ctx.scopes)
|
|
};
|
|
}
|
|
|
|
function execFunctionDef(ctx, ast) {
|
|
const scope = ctx.scopes[0]; // Lets put functions always in top scope.
|
|
// const scope = ctx.scopes[ctx.scopes.length-1];
|
|
if (getScope(ctx, ast.name)) {
|
|
return error(ctx, ast, "Name already exists: "+ast.name);
|
|
}
|
|
ctx.functionParams[ast.name] = ast.params;
|
|
scope[ast.name] = {
|
|
type: "FUNCTION",
|
|
params: ast.params,
|
|
block: ast.block,
|
|
fileName: ctx.fileName,
|
|
filePath: ctx.filePath,
|
|
scopes: copyScope(ctx.scopes)
|
|
};
|
|
}
|
|
|
|
|
|
function execDeclareComponent(ctx, ast) {
|
|
const scope = ctx.scopes[ctx.scopes.length-1];
|
|
|
|
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
|
|
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name);
|
|
|
|
const baseName = ctx.currentComponent ? ctx.currentComponent + "." + ast.name.name : ast.name.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.type != "NUMBER") return error(ctx, ast.name.selectors[i], "expected a number");
|
|
|
|
sizes.push( size.value.toJSNumber() );
|
|
}
|
|
|
|
|
|
scope[ast.name.name] = iterateSelectors(ctx, sizes, baseName, function(fullName) {
|
|
|
|
ctx.components[fullName] = "UNINSTANTIATED";
|
|
|
|
return {
|
|
type: "COMPONENT",
|
|
fullName: fullName
|
|
};
|
|
});
|
|
|
|
return {
|
|
type: "VARIABLE",
|
|
name: ast.name.name,
|
|
selectors: []
|
|
};
|
|
}
|
|
|
|
function execInstantiateComponet(ctx, vr, fn) {
|
|
|
|
if (vr.type != "VARIABLE") return error(ctx, fn, "Left hand instatiate component must be a variable");
|
|
if (fn.type != "FUNCTIONCALL") return error(ctx, fn, "Right type of instantiate component must be a function call");
|
|
|
|
const componentName = vr.name;
|
|
const templateName = fn.name;
|
|
|
|
const scopeLevel = getScopeLevel(ctx, templateName);
|
|
if (scopeLevel == -1) return error(ctx,fn, "Invalid Template");
|
|
const template = getScope(ctx, templateName);
|
|
|
|
if (template.type != "TEMPLATE") return error(ctx, 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;
|
|
|
|
paramValues.push(v);
|
|
}
|
|
if (template.params.length != paramValues.length) error(ctx, fn, "Invalid Number of parameters");
|
|
|
|
const vv = getScope(ctx, componentName, vr.selectors);
|
|
|
|
if (!vv) return error(ctx, vr, "Component not defined"+ componentName);
|
|
|
|
instantiateComponent(vv);
|
|
|
|
function instantiateComponent(varVal) {
|
|
|
|
function extractValue(v) {
|
|
if (Array.isArray(v)) {
|
|
return v.map(extractValue);
|
|
} else {
|
|
return v.value.toString();
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(varVal)) {
|
|
for (let i =0; i<varVal.length; i++) {
|
|
instantiateComponent(varVal[i]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ctx.components[varVal.fullName] != "UNINSTANTIATED") error(ctx, fn, "Component already instantiated");
|
|
|
|
const oldComponent = ctx.currentComponent;
|
|
const oldFileName = ctx.fileName;
|
|
const oldFilePath = ctx.filePath;
|
|
ctx.currentComponent = varVal.fullName;
|
|
|
|
ctx.components[ctx.currentComponent] = {
|
|
signals: [],
|
|
params: {}
|
|
};
|
|
|
|
const oldScopes = ctx.scopes;
|
|
|
|
ctx.scopes = oldScopes.slice(0, scopeLevel+1);
|
|
|
|
if (template.params.length != paramValues.length) return error(ctx, fn, "Invalid number of parameters: " + templateName);
|
|
|
|
const scope = {};
|
|
for (let i=0; i< template.params.length; i++) {
|
|
scope[template.params[i]] = paramValues[i];
|
|
ctx.components[ctx.currentComponent].params[template.params[i]] = extractValue(paramValues[i]);
|
|
}
|
|
|
|
ctx.components[ctx.currentComponent].template = templateName;
|
|
ctx.fileName = template.fileName;
|
|
ctx.filePath = template.filePath;
|
|
ctx.scopes = copyScope( template.scopes );
|
|
ctx.scopes.push(scope);
|
|
|
|
execBlock(ctx, template.block);
|
|
|
|
ctx.fileName = oldFileName;
|
|
ctx.filePath = oldFilePath;
|
|
ctx.currentComponent = oldComponent;
|
|
ctx.scopes = oldScopes;
|
|
}
|
|
}
|
|
|
|
function execFunctionCall(ctx, ast) {
|
|
|
|
if (ast.name == "log") {
|
|
const v = exec(ctx, ast.params[0]);
|
|
console.log(v.value.toString());
|
|
return;
|
|
}
|
|
|
|
const scopeLevel = getScopeLevel(ctx, ast.name);
|
|
if (scopeLevel == -1) return error(ctx, ast, "Function not defined: " + ast.name);
|
|
const fnc = getScope(ctx, ast.name);
|
|
|
|
if (fnc.type != "FUNCTION") return error(ctx, ast, "Not a function: " + ast.name);
|
|
|
|
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) error(ctx, ast, "Invalid Number of parameters");
|
|
|
|
const oldFileName = ctx.fileName;
|
|
const oldFilePath = ctx.filePath;
|
|
|
|
const oldScopes = ctx.scopes;
|
|
|
|
ctx.scopes = oldScopes.slice(0, scopeLevel+1);
|
|
|
|
const scope = {};
|
|
for (let i=0; i< fnc.params.length; i++) {
|
|
scope[fnc.params[i]] = paramValues[i];
|
|
}
|
|
|
|
ctx.fileName = fnc.fileName;
|
|
ctx.filePath = fnc.filePath;
|
|
ctx.scopes = copyScope( fnc.scopes );
|
|
ctx.scopes.push(scope);
|
|
|
|
execBlock(ctx, fnc.block);
|
|
|
|
const res = ctx.returnValue;
|
|
ctx.returnValue = null;
|
|
|
|
ctx.fileName = oldFileName;
|
|
ctx.filePath = oldFilePath;
|
|
ctx.scopes = oldScopes;
|
|
|
|
return res;
|
|
}
|
|
|
|
function execReturn(ctx, ast) {
|
|
ctx.returnValue = exec(ctx, ast.value);
|
|
return;
|
|
}
|
|
|
|
function execDeclareSignal(ctx, ast) {
|
|
const scope = ctx.scopes[ctx.scopes.length-1];
|
|
|
|
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
|
|
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name);
|
|
|
|
const baseName = ctx.currentComponent ? ctx.currentComponent + "." + ast.name.name : ast.name.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.type != "NUMBER") return error(ctx, ast.name.selectors[i], "expected a number");
|
|
sizes.push( size.value.toJSNumber() );
|
|
}
|
|
|
|
scope[ast.name.name] = iterateSelectors(ctx, sizes, baseName, function(fullName) {
|
|
ctx.signals[fullName] = {
|
|
fullName: fullName,
|
|
direction: ast.declareType == "SIGNALIN" ? "IN" : (ast.declareType == "SIGNALOUT" ? "OUT" : ""),
|
|
private: ast.private,
|
|
component: ctx.currentComponent,
|
|
equivalence: "",
|
|
alias: [fullName]
|
|
};
|
|
ctx.components[ctx.currentComponent].signals.push(fullName);
|
|
return {
|
|
type: "SIGNAL",
|
|
fullName: fullName,
|
|
};
|
|
});
|
|
return {
|
|
type: "VARIABLE",
|
|
name: ast.name.name,
|
|
selectors: []
|
|
};
|
|
}
|
|
|
|
function execDeclareVariable(ctx, ast) {
|
|
const scope = ctx.scopes[ctx.scopes.length-1];
|
|
|
|
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid linear combination name");
|
|
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.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.type != "NUMBER") return error(ctx, ast.name.selectors[i], "expected a number");
|
|
sizes.push( size.value.toJSNumber() );
|
|
}
|
|
|
|
scope[ast.name.name] = iterateSelectors(ctx, sizes, "", function() {
|
|
return {
|
|
type: "NUMBER",
|
|
value: bigInt(0)
|
|
};
|
|
});
|
|
|
|
return {
|
|
type: "VARIABLE",
|
|
name: ast.name.name,
|
|
selectors: []
|
|
};
|
|
}
|
|
|
|
function execVariable(ctx, ast) {
|
|
let v;
|
|
try {
|
|
v = getScope(ctx, ast.name, ast.selectors);
|
|
} catch(err) {
|
|
console.log(JSON.stringify(ast, null,1));
|
|
}
|
|
if (ctx.error) return;
|
|
|
|
if (!v) return error(ctx, ast, "Variable not defined");
|
|
|
|
// If the signal has an assigned value (constant) just return the constant
|
|
if ((v.type == "SIGNAL") && (ctx.signals[v.fullName].value)) {
|
|
return {
|
|
type: "NUMBER",
|
|
value: ctx.signals[v.fullName].value
|
|
};
|
|
}
|
|
let res;
|
|
res=v;
|
|
return res;
|
|
}
|
|
|
|
function execPin(ctx, ast) {
|
|
const component = getScope(ctx, ast.component.name, ast.component.selectors);
|
|
if (!component) return error(ctx, ast.component, "Component does not exists: "+ast.component.name);
|
|
if (ctx.error) return;
|
|
let signalFullName = component.fullName + "." + ast.pin.name;
|
|
for (let i=0; i< ast.pin.selectors.length; i++) {
|
|
const sel = exec(ctx, ast.pin.selectors[i]);
|
|
if (ctx.error) return;
|
|
|
|
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
|
|
|
|
signalFullName += "[" + sel.value.toJSNumber() + "]";
|
|
}
|
|
if (!ctx.signals[signalFullName]) error(ctx, ast, "Signal not defined:" + signalFullName);
|
|
return {
|
|
type: "SIGNAL",
|
|
fullName: signalFullName
|
|
};
|
|
}
|
|
|
|
function execFor(ctx, ast) {
|
|
|
|
ctx.scopes.push({});
|
|
exec(ctx, ast.init);
|
|
if (ctx.error) return;
|
|
|
|
let v = exec(ctx, ast.condition);
|
|
if (ctx.error) return;
|
|
|
|
if (typeof v.value != "undefined") {
|
|
while ((v.value.neq(0))&&(!ctx.returnValue)) {
|
|
exec(ctx, ast.body);
|
|
if (ctx.error) return;
|
|
|
|
exec(ctx, ast.step);
|
|
if (ctx.error) return;
|
|
|
|
v = exec(ctx, ast.condition);
|
|
if (ctx.error) return;
|
|
}
|
|
}
|
|
ctx.scopes.pop();
|
|
}
|
|
|
|
function execWhile(ctx, ast) {
|
|
let v = exec(ctx, ast.condition);
|
|
if (ctx.error) return;
|
|
|
|
if (typeof v.value != "undefined") {
|
|
while ((v.value.neq(0))&&(!ctx.returnValue)) {
|
|
exec(ctx, ast.body);
|
|
if (ctx.error) return;
|
|
|
|
v = exec(ctx, ast.condition);
|
|
if (ctx.error) return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function execIf(ctx, ast) {
|
|
let v = exec(ctx, ast.condition);
|
|
if (ctx.error) return;
|
|
|
|
if (typeof v.value != "undefined") {
|
|
if ((v.value.neq(0))&&(!ctx.returnValue)) {
|
|
exec(ctx, ast.then);
|
|
if (ctx.error) return;
|
|
} else {
|
|
if (ast.else) {
|
|
exec(ctx, ast.else);
|
|
if (ctx.error) return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function execVarAssignement(ctx, ast) {
|
|
let v;
|
|
if (ast.values[0].type == "DECLARE") {
|
|
v = exec(ctx, ast.values[0]);
|
|
if (ctx.error) return;
|
|
} else {
|
|
v = ast.values[0];
|
|
}
|
|
const num = getScope(ctx, v.name, v.selectors);
|
|
if (ctx.error) return;
|
|
|
|
if ((typeof(num) != "object")||(num == null)) return error(ctx, ast, "Variable not defined");
|
|
|
|
if (num.type == "COMPONENT") return execInstantiateComponet(ctx, v, ast.values[1]);
|
|
if (ctx.error) return;
|
|
// if (num.type == "SIGNAL") return error(ctx, ast, "Cannot assign to a signal with `=` use <-- or <== ops");
|
|
|
|
const res = exec(ctx, ast.values[1]);
|
|
if (ctx.error) return;
|
|
|
|
setScope(ctx, v.name, v.selectors, 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 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 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, { 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, { 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, { 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, { 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, { 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, { 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.fullName];
|
|
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.fullName);
|
|
while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence];
|
|
|
|
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") {
|
|
sDest.equivalence = src.fullName;
|
|
sDest.alias = sDest.alias.concat(src.alias);
|
|
while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence];
|
|
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.value = 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));
|
|
}
|
|
|
|
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;
|
|
|
|
ctx.includedFiles[incFileName] = true;
|
|
|
|
const src = fs.readFileSync(incFileName, "utf8");
|
|
|
|
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<ast.values.length; i++) {
|
|
res.push(exec(ctx, ast.values[i]));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
function copyScope(scope) {
|
|
var scopesClone = [];
|
|
for (let i=0; i<scope.length; i++) {
|
|
scopesClone.push(scope[i]);
|
|
}
|
|
return scopesClone;
|
|
}
|
|
|