circom/src/construction_phase.js
src/construction_phase.jsRaw
1/*
2 Copyright 2018 0KIMS association.
3
4 This file is part of circom (Zero Knowledge Circuit Compiler).
5
6 circom is a free software: you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 circom is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with circom. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20/*
21
22 The exec functions, return an object of the form:
23
24 {
25 t: "C" or "V" or "S"
26 s: Accumulated Sizes
27 v: [] only when "V" Array of values where each value is:
28 {
29 t: "N", "S", "LC", "QEX", "NQ"
30 v: When "N" its the bigInt of the number
31 coefs: { sIdx: vCoef } when "LC" the values of the coefs
32 a,b,c: a LC object of a quadratic expression.
33 }
34 refId: In case of a variable, the reference of a variable.
35 sIdx: When signal, the sIdx
36 cIdx: When component, the cIdx
37 }
38
39
40
41
42
43 */
44
45const path = require("path");
46const fs = require("fs");
47const assert = require("assert");
48
49const iterateAST = require("./iterateast");
50const utils = require("./utils");
51
52const LCAlgebra = require("./lcalgebra");
53const parser = require("../parser/jaz.js").parser;
54const Scalar = require("ffjavascript").Scalar;
55
56const {stringifyBigInts} = require("ffjavascript").utils;
57
58/* TODO: Add lines information
59
60function setLines(dst, first, last) {
61 last = last || first;
62 dst.first_line = first.first_line;
63 dst.first_column = first.first_column;
64 dst.last_line = last.last_line;
65 dst.last_column = last.last_column;
66}
67*/
68
69module.exports = constructionPhase;
70
71const NQVAL = {t: "V", s:[1,0], v: [{t:"NQ"}]};
72
73function constructionPhase(ctx, srcFile) {
74
75 const fullFileName = srcFile;
76 const fullFilePath = path.dirname(fullFileName);
77
78 const src = fs.readFileSync(fullFileName, "utf8");
79 ctx.ast = parser.parse(src);
80
81 assert(ctx.ast.type == "BLOCK");
82
83 ctx.lc = new LCAlgebra(ctx.F);
84 ctx.filePath= fullFilePath;
85 ctx.fileName= fullFileName;
86 ctx.includedFiles = {};
87 ctx.includedFiles[fullFileName] = src.split("\n");
88
89 ctx.refs = [];
90 createRefs(ctx, ctx.ast);
91 exec(ctx, ctx.ast);
92
93}
94
95function exec(ctx, ast) {
96 if (!ast) {
97 return ctx.throwError(ast, "Null AST");
98 }
99 if (ast.type == "NUMBER") {
100 return execNumber(ctx,ast);
101 } else if (ast.type == "VARIABLE") {
102 return execVariable(ctx, ast);
103 } else if (ast.type == "PIN") {
104 return execPin(ctx, ast);
105 } else if (ast.type == "OP") {
106 if (ast.op == "=") {
107 return execAssignement(ctx, ast);
108 } else if (ast.op == "<--") {
109 return execAssignement(ctx, ast);
110 } else if (ast.op == "<==") {
111 return execSignalAssignConstrain(ctx, ast);
112 } else if (ast.op == "===") {
113 return execConstrain(ctx, ast);
114 } else if (ast.op == "+=") {
115 return execOpOp(ctx, ast, "add", "LEFT");
116 } else if (ast.op == "*=") {
117 return execOpOp(ctx, ast, "mul", "LEFT");
118 } else if (ast.op == "+") {
119 return execOp(ctx, ast, "add", 2);
120 } else if (ast.op == "-") {
121 return execOp(ctx, ast, "sub", 2);
122 } else if (ast.op == "UMINUS") {
123 return execOp(ctx, ast, "neg", 1);
124 } else if (ast.op == "*") {
125 return execOp(ctx, ast, "mul", 2);
126 } else if (ast.op == "%") {
127 return execOp(ctx, ast, "mod", 2);
128 } else if (ast.op == "PLUSPLUSRIGHT") {
129 return execOpOp(ctx, ast, "add", "RIGHT");
130 } else if (ast.op == "PLUSPLUSLEFT") {
131 return execOpOp(ctx, ast, "add", "LEFT");
132 } else if (ast.op == "MINUSMINUSRIGHT") {
133 return execOpOp(ctx, ast, "sub", "RIGHT");
134 } else if (ast.op == "MINUSMINUSLEFT") {
135 return execOpOp(ctx, ast, "sub", "LEFT");
136 } else if (ast.op == "/") {
137 return execOp(ctx, ast, "div", 2);
138 } else if (ast.op == "\\") {
139 return execOp(ctx, ast, "idiv", 2);
140 } else if (ast.op == "**") {
141 return execOp(ctx, ast, "pow", 2);
142 } else if (ast.op == "&") {
143 return execOp(ctx, ast, "band", 2);
144 } else if (ast.op == "|") {
145 return execOp(ctx, ast, "bor", 2);
146 } else if (ast.op == "^") {
147 return execOp(ctx, ast, "bxor", 2);
148 } else if (ast.op == "~") {
149 return execOp(ctx, ast, "bnot", 1);
150 } else if (ast.op == "&&") {
151 return execOp(ctx, ast, "land", 2);
152 } else if (ast.op == "||") {
153 return execOp(ctx, ast, "lor", 2);
154 } else if (ast.op == "!") {
155 return execOp(ctx, ast, "lnot", 1);
156 } else if (ast.op == "<<") {
157 return execOp(ctx, ast, "shl", 2);
158 } else if (ast.op == ">>") {
159 return execOp(ctx, ast, "shr", 2);
160 } else if (ast.op == "<") {
161 return execOp(ctx, ast, "lt", 2);
162 } else if (ast.op == ">") {
163 return execOp(ctx, ast, "gt", 2);
164 } else if (ast.op == "<=") {
165 return execOp(ctx, ast, "leq", 2);
166 } else if (ast.op == ">=") {
167 return execOp(ctx, ast, "geq", 2);
168 } else if (ast.op == "==") {
169 return execOp(ctx, ast, "eq", 2);
170 } else if (ast.op == "!=") {
171 return execOp(ctx, ast, "neq", 2);
172 } else if (ast.op == "?") {
173 return execTerCon(ctx, ast);
174 } else {
175 ctx.throwError(ast, "Invalid operation: " + ast.op);
176 }
177 } else if (ast.type == "DECLARE") {
178 if (ast.declareType == "COMPONENT") {
179 return execDeclareComponent(ctx, ast);
180 } else if ((ast.declareType == "SIGNALIN")||
181 (ast.declareType == "SIGNALOUT")||
182 (ast.declareType == "SIGNAL")) {
183 return execDeclareSignal(ctx, ast);
184 } else if (ast.declareType == "VARIABLE") {
185 return execDeclareVariable(ctx, ast);
186 } else {
187 ctx.throwError(ast, "Invalid declaration: " + ast.declareType);
188 }
189 } else if (ast.type == "FUNCTIONCALL") {
190 return execFunctionCall(ctx, ast);
191 } else if (ast.type == "BLOCK") {
192 return execBlock(ctx, ast);
193 } else if (ast.type == "COMPUTE") {
194 return execCompute(ctx, ast);
195 } else if (ast.type == "FOR") {
196 return execLoop(ctx, ast);
197 } else if (ast.type == "WHILE") {
198 return execLoop(ctx, ast);
199 } else if (ast.type == "IF") {
200 return execIf(ctx, ast);
201 } else if (ast.type == "RETURN") {
202 return execReturn(ctx, ast);
203 } else if (ast.type == "TEMPLATEDEF") {
204 return execTemplateDef(ctx, ast);
205 } else if (ast.type == "FUNCTIONDEF") {
206 return execFunctionDef(ctx, ast);
207 } else if (ast.type == "INCLUDE") {
208 return execInclude(ctx, ast);
209 } else if (ast.type == "ARRAY") {
210 return execArray(ctx, ast);
211 } else {
212 ctx.throwError(ast, "Invalid AST node type: " + ast.type);
213 }
214}
215
216function execNumber(ctx, ast) {
217 return {
218 t: "V",
219 s:[1,0],
220 v: [{
221 t: "N",
222 v: ctx.F.e(ast.value)
223 }]
224 };
225}
226
227
228function execTemplateDef(ctx, ast) {
229 ctx.templates[ast.name] = {
230 block: ast.block,
231 params: ast.params,
232 fileName: ctx.fileName,
233 filePath: ctx.filePath,
234 };
235}
236
237function execFunctionDef(ctx, ast) {
238 ctx.functions[ast.name] = {
239 block: ast.block,
240 params: ast.params,
241 fileName: ctx.fileName,
242 filePath: ctx.filePath
243 };
244}
245
246function execDeclareComponent(ctx, ast) {
247 if (ast.name.type != "VARIABLE") return ctx.throwError( ast, "Invalid component name");
248
249 const sizes=[];
250 for (let i=0; i< ast.name.selectors.length; i++) {
251 const sizeRef = exec(ctx, ast.name.selectors[i]);
252 if (ctx.error) return;
253
254 const size = val(ctx, sizeRef);
255 if (size.t != "N") return ctx.throwError( ast.name.selectors[i], "expected a number");
256
257 sizes.push( Scalar.toNumber(size.v) );
258 }
259
260 let cIdx = ctx.addComponent(ast.name.name, sizes);
261 if (!Array.isArray(cIdx)) cIdx = [cIdx, cIdx+1];
262
263
264 ctx.refs[ast.refId].s = utils.accSizes(sizes);
265 ctx.refs[ast.refId].cIdx = cIdx[0];
266
267 return ctx.refs[ast.refId];
268}
269
270
271function execDeclareSignal(ctx, ast) {
272
273 if (ast.name.type != "VARIABLE") return ctx.throwError(ast, "Invalid component name");
274
275 const sizes=[];
276 for (let i=0; i< ast.name.selectors.length; i++) {
277 const size = exec(ctx, ast.name.selectors[i]);
278 if (ctx.error) return;
279 if (size.s[0] != 1) return ctx.throwError(ast, "Size cannot be an array");
280 if (size.v[0].t != "N") return ctx.throwError(ast, "Size must be declared in construction time");
281 sizes.push( Scalar.toNumber(size.v[0].v) );
282 }
283
284 let sIdx = ctx.addSignal(ast.name.name, sizes);
285 if (!Array.isArray(sIdx)) sIdx = [sIdx, sIdx+1];
286 for (let i=sIdx[0]; i<sIdx[1]; i++) {
287 ctx.signals[i] = {
288 o: 0,
289 e: -1
290 };
291
292 if (ast.declareType == "SIGNALIN") {
293 ctx.signals[i].o |= ctx.IN;
294 ctx.components[ctx.currentComponent].nInSignals+=1;
295 }
296 if (ast.declareType == "SIGNALOUT") {
297 ctx.signals[i].o |= ctx.OUT;
298 }
299 if (ast.private ) {
300 ctx.signals[i].o |= ctx.PRV;
301 }
302 if (ctx.main) {
303 ctx.signals[i].o |= ctx.MAIN;
304 }
305
306 // ctx.components[ctx.currentComponent].signals.push(i);
307 }
308
309 const v = ctx.refs[ast.refId];
310 v.s = utils.accSizes(sizes);
311 v.sIdx = sIdx[0];
312
313 return v;
314}
315
316function execDeclareVariable(ctx, ast) {
317
318 if (ast.name.type != "VARIABLE") return ctx.throwError(ast, "Invalid linear combination name");
319
320 const sizes=[];
321 for (let i=0; i< ast.name.selectors.length; i++) {
322 const size = exec(ctx, ast.name.selectors[i]);
323 if (ctx.error) return;
324 if (size.s[0] != 1) return ctx.throwError(ast, "Size cannot be an array");
325 if (size.v[0].t != "N") return ctx.throwError(ast, "Size must be declared in construction time");
326 sizes.push( Scalar.toNumber(size.v[0].v) );
327 }
328
329 const v = ctx.refs[ast.refId];
330
331 v.s = utils.accSizes(sizes);
332 v.v = new Array(v.s[0]);
333
334 return v;
335}
336
337function execAssignement(ctx, ast) {
338 let left;
339 const leftSels=[];
340 if (ast.values[0].type == "DECLARE") {
341 left = exec(ctx, ast.values[0]);
342 if (ctx.error) return;
343 } else if (ast.values[0].type == "PIN") {
344 left = execPin(ctx, ast.values[0]);
345 if (ctx.error) return;
346 } else {
347 if (!utils.isDefined(ast.values[0].refId)) return ctx.throwError(ast, "Assigning to a non variable");
348 left = ctx.refs[ast.values[0].refId];
349 if (ast.values[0].selectors) {
350 for (let i=0; i< ast.values[0].selectors.length; i++) {
351 const sel = exec(ctx, ast.values[0].selectors[i]);
352 if (ctx.error) return;
353
354 if (sel.s[0] != 1) return ctx.throwError(ast, "Selector cannot be an array");
355 if (sel.v[0].t != "N") return {t: "NQ"};
356
357 leftSels.push( Scalar.toNumber(sel.v[0].v) );
358 }
359 }
360
361 }
362
363 if ((!left)||(!left.s)) return ctx.throwError(ast, "variable. not defined yet");
364
365 if (left.t == "C") return execInstantiateComponet(ctx, left, ast.values[1], leftSels);
366 if ((left.t == "S")&&( ["<--", "<==", "-->", "==>"].indexOf(ast.op) < 0)) return ctx.throwError(ast, "Cannot assign to a signal with `=` use <-- or <== ops");
367 if ((left.t == "V")&&( ["<--", "<==", "-->", "==>"].indexOf(ast.op) >= 0)) return ctx.throwError(ast, `Cannot assign to a var with ${ast.op}. use = op`);
368
369 const right = exec(ctx, ast.values[1]);
370 if (ctx.error) return;
371
372 if (!utils.sameSizes(left.s.slice(leftSels.length),right.s)) return ctx.throwError(ast, "Sizes in assignment must be the same");
373
374 let o = 0;
375 for (let i=0; i<leftSels.length; i++) o += leftSels[i]*left.s[i+1];
376 if (left.t == "V") {
377 if (right.t=="V") {
378 for (let i=0; i<right.s[0]; i++) {
379 left.v[o+i]=right.v[i];
380 }
381 } else if (right.t == "S") {
382 for (let i=0; i<right.s[0]; i++) {
383 left.v[o+i]={t: "LC", coefs: {}};
384 left.v[o+i].coefs[right.sIdx+i] = ctx.F.one;
385 }
386 }
387 } else if ( left.t == "S") {
388 if (right.t=="V") {
389 for (let i=0; i<right.s[0]; i++) {
390 const ev = ctx.lc.evaluate(ctx, right.v[i]);
391 setSignalValue(left.sIdx + o +i, ev);
392 }
393 } else if (right.t == "S") {
394 for (let i=0; i<right.s[0]; i++) {
395 joinSignals(left.sIdx + o + i, right.sIdx + i);
396 }
397 }
398 }
399
400 return right;
401
402 function setSignalValue(dSIdx, v) {
403 let sDest = ctx.signals[dSIdx];
404 while (sDest.e>=0) sDest=ctx.signals[sDest.e];
405
406 if (utils.isDefined(sDest.v)) return ctx.throwError(ast, "Signals cannot be assigned twice");
407
408 if (v !== null) {
409 sDest.v = v;
410 }
411 }
412
413 function joinSignals(dIdx, sIdx) {
414 let sDest=ctx.signals[dIdx];
415 let isOut = (sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT);
416 while (sDest.e>=0) {
417 sDest=ctx.signals[sDest.e];
418 isOut = isOut || ((sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT));
419 }
420
421 if (utils.isDefined(sDest.v)) return ctx.throwError(ast, "Signals cannot be assigned twice");
422
423 let sSrc = ctx.signals[sIdx];
424 let isIn = (sSrc.o & ctx.MAIN)&&(sSrc.o & ctx.IN);
425 while (sSrc.e>=0) {
426 sSrc=ctx.signals[sSrc.e];
427 isIn = isIn || ((sSrc.o & ctx.MAIN)&&(sSrc.o & ctx.IN));
428 }
429
430 // Skip if an out is assigned directly to an input.
431 if (!(isIn&&isOut)) {
432 if (isIn) {
433 sDest.e = sIdx;
434 } else if (isOut) {
435 sSrc.e = dIdx;
436 } else {
437 sDest.e = sIdx;
438 }
439 if (!isOut) {
440 if (utils.isDefined(sSrc.v)) sDest.v = sSrc.v;
441 }
442 }
443 }
444}
445
446
447
448function execInstantiateComponet(ctx, vr, fn, sels) {
449
450 if (fn.type != "FUNCTIONCALL") return ctx.throwError(fn, "Right type of instantiate component must be a function call");
451
452 const templateName = fn.name;
453
454 const template = ctx.templates[templateName];
455 if (!template) return ctx.throwError(fn, "Invalid Template");
456
457 const paramValues = [];
458 for (let i=0; i< fn.params.length; i++) {
459 const v = exec(ctx, fn.params[i]);
460 if (ctx.error) return;
461 for (let j=0; j<v.s[0]; j++) {
462 if (v.v[j].t != "N") ctx.throwError(fn, "Parameters of a template must be constant");
463 }
464 paramValues.push(v);
465 }
466 if (template.params.length != paramValues.length) ctx.throwError(fn, "Invalid Number of parameters");
467
468 let o=0;
469 for (let i=0; i<sels.length; i++) o += sels[i] * vr.s[i+1];
470 for (let i=o; i<o+vr.s[sels.length]; i++) {
471 instantiateComponent(vr.cIdx+i);
472 }
473
474 function instantiateComponent(cIdx) {
475
476 function extractValue(sizes, arr) {
477 if (sizes[0] == 1) return arr[0].v.toString();
478 const res = [];
479 for (let i=0; i<sizes[0]; i += sizes[1]) {
480 res.push(extractValue(sizes.slice(1), arr.slice(i, i+sizes[1])));
481 }
482 return res;
483 }
484
485 if (ctx.components[cIdx]) return ctx.throwError(fn, "Component already instantiated");
486
487 const oldComponent = ctx.currentComponent;
488 const oldFileName = ctx.fileName;
489 const oldFilePath = ctx.filePath;
490 const oldMain = ctx.main;
491 const oldRefs = ctx.refs;
492
493 if (ctx.currentComponent==-1) {
494 ctx.main=true;
495 } else {
496 ctx.main=false;
497 }
498
499 ctx.currentComponent = cIdx;
500
501 ctx.components[cIdx] = {
502 params: {},
503 names: ctx.newTableName(),
504 nInSignals: 0,
505 template: templateName
506 };
507
508
509 if (template.params.length != paramValues.length) return ctx.throwError(fn, "Invalid number of parameters: " + templateName);
510 ctx.refs = [];
511
512 const scope = {};
513 for (let i=0; i< template.params.length; i++) {
514 ctx.refs.push({
515 t: "V",
516 s: paramValues[i].s,
517 v: paramValues[i].v,
518 refId: i
519 });
520 scope[template.params[i]] = i;
521 ctx.components[cIdx].params[template.params[i]] = extractValue(paramValues[i].s, paramValues[i].v);
522 }
523
524 createRefs(ctx, template.block, scope);
525
526 ctx.fileName = template.fileName;
527 ctx.filePath = template.filePath;
528
529 execBlock(ctx, template.block);
530
531 ctx.refs = oldRefs;
532 ctx.fileName = oldFileName;
533 ctx.filePath = oldFilePath;
534 ctx.currentComponent = oldComponent;
535 ctx.main = oldMain;
536 }
537}
538
539
540function execBlock(ctx, ast) {
541 for (let i=0; i<ast.statements.length; i++) {
542 exec(ctx, ast.statements[i]);
543 if (ctx.returnValue) return;
544 if (ctx.error) return;
545 }
546}
547
548function clone(a) {
549 const res = {
550 t: a.t,
551 s: a.s
552 };
553 if (a.t == "V") {
554 res.v = new Array(a.v.length);
555 for (let i=0; i<a.v.length; i++) res.v[i] = a.v[i];
556 } else if (a.t == "S") {
557 res.sIdx = a.sIdx;
558 } else if (a.t == "C") {
559 res.cIdx = a.cIdx;
560 }
561 return res;
562}
563
564function execFunctionCall(ctx, ast) {
565
566 if (ast.name == "log") {
567 const v = exec(ctx, ast.params[0]);
568 const ev = val(ctx, v, ast);
569 if (ev.v) {
570 console.log(ev.v.toString());
571 } else {
572 console.log(JSON.stringify(stringifyBigInts(ev)));
573 }
574 return;
575 }
576 if (ast.name == "assert") {
577 ast.fileName = ctx.fileName;
578 ast.filePath = ctx.filePath;
579
580 const v = exec(ctx, ast.params[0]);
581 const ev = val(ctx, v, ast);
582 if ((typeof ev.v !== "undefined")&&(ctx.F.isZero(ev.v))) return ctx.throwError(ast, "Assertion failed");
583 return;
584 }
585
586 const fnc = ctx.functions[ast.name];
587
588 if (!fnc) return ctx.throwError(ast, "Function not defined");
589
590 const paramValues = [];
591 for (let i=0; i< ast.params.length; i++) {
592 const v = exec(ctx, ast.params[i]);
593 if (ctx.error) return;
594
595 paramValues.push(v);
596 }
597
598 if (ast.params.length != paramValues.length) ctx.throwError(ast, "Invalid Number of parameters");
599
600 const oldFileName = ctx.fileName;
601 const oldFilePath = ctx.filePath;
602 const oldRefs = ctx.refs;
603
604 ctx.fileName = fnc.fileName;
605 ctx.filePath = fnc.filePath;
606 ctx.refs = [];
607
608 const scope = {};
609 for (let i=0; i< fnc.params.length; i++) {
610 const entry = clone(paramValues[i]);
611 entry.refId = i;
612 scope[fnc.params[i]] = i;
613 ctx.refs.push(entry);
614 }
615
616 createRefs(ctx, fnc.block, scope);
617
618 execBlock(ctx, fnc.block);
619
620 const res = ctx.returnValue;
621 ctx.returnValue = null;
622
623 ctx.fileName = oldFileName;
624 ctx.filePath = oldFilePath;
625 ctx.refs = oldRefs;
626
627 return res;
628}
629
630function execReturn(ctx, ast) {
631 ctx.returnValue = exec(ctx, ast.value);
632 return;
633}
634
635
636function execVariable(ctx, ast) {
637
638 const v = ctx.refs[ast.refId];
639 if (!v) {
640 return ctx.throwError(ast, "Variable not defined: "+ast.name);
641 }
642
643 const sels = [];
644 for (let i=0; i< ast.selectors.length; i++) {
645 const sel = exec(ctx, ast.selectors[i]);
646 if (ctx.error) return;
647 if (sel.s[0] != 1) return ctx.throwError(ast, "Variable selector cannot be an array");
648 if (sel.v[0].t != "N") return NQVAL;
649 sels.push(Scalar.toNumber(sel.v[0].v));
650 }
651
652 let o = 0;
653 let s = v.s[0];
654
655 if (sels.length > v.s.length-2) return ctx.throwError(ast, "Too many selectors");
656 for (let i=0; i<sels.length; i++) {
657 if (sels[i] >= v.s[i] / v.s[i+1]) return ctx.throwError(ast, "Out of Range");
658 if (sels[i] < 0 ) return ctx.throwError(ast, "selector negative");
659 o += sels[i] * v.s[i+1];
660 s = v.s[i+1];
661 }
662
663 if (v.t == "V") {
664 return {
665 t: "V",
666 s: v.s.slice(sels.length),
667 v: v.v.slice(o, o+s)
668 };
669 } else if (v.t == "S") {
670 return {
671 t: "S",
672 s: v.s.slice(sels.length),
673 sIdx: o + v.sIdx
674 };
675 } else if (v.t == "C") {
676 return {
677 t: "C",
678 s: v.s.slice(sels.length),
679 cIdx: o
680 };
681 } else {
682 assert(false);
683 }
684}
685
686
687
688
689function execPin(ctx, ast) {
690 const selsC = [];
691 for (let i=0; i< ast.component.selectors.length; i++) {
692 const sel = exec(ctx, ast.component.selectors[i]);
693 if (ctx.error) return;
694
695 if (sel.s[0] != 1) return ctx.throwError(ast, "Component selector cannot be an array");
696 if (sel.v[0].t != "N") return NQVAL;
697 selsC.push(Scalar.toNumber(sel.v[0].v));
698 }
699
700 const cIdx = ctx.getComponentIdx(ast.component.name, selsC);
701 if (cIdx<0) return ctx.throwError(ast.component, "Component does not exists: "+ast.component.name);
702
703 const selsP = [];
704 for (let i=0; i< ast.pin.selectors.length; i++) {
705 const sel = exec(ctx, ast.pin.selectors[i]);
706 if (ctx.error) return;
707 if (sel.s[0] != 1) return ctx.throwError(ast, "Signal selector cannot be an array");
708 if (sel.v[0].t != "N") return NQVAL;
709 selsP.push(Scalar.toNumber(sel.v[0].v));
710 }
711 if (!ctx.components[cIdx]) {
712 return ctx.throwError(ast, "Component not defined yet");
713 }
714 const sIdx = ctx.components[cIdx].names.getSignalIdx(ast.pin.name, selsP);
715
716 if (sIdx<0) return ctx.throwError(ast, "Signal not defined:" + buildFullName() );
717 return {
718 t: "S",
719 sIdx: sIdx,
720 s: utils.accSizes(ctx.components[cIdx].names.o[ast.pin.name].sizes).slice(selsP.length)
721 };
722
723 function buildFullName() {
724 return ast.component.name + sels2str(selsC) + "." + ast.pin.name + sels2str(selsP);
725 }
726
727 function sels2str(sels) {
728 let S = "";
729 for (let i=0; i< sels.length; i++) {
730 S += "[" + sels[i] + "]";
731 }
732 return S;
733 }
734}
735
736function execLoop(ctx, ast) {
737
738 if (ast.init) {
739 exec(ctx, ast.init);
740 if (ctx.error) return;
741 }
742
743 let v = exec(ctx, ast.condition);
744 if (ctx.error) return;
745 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition in loop cannot be an array");
746 if (v.v[0].t != "N") {
747 iterateAST(ast, (ast) => {
748 if (ast.type == "OP") {
749 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
750 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
751 }
752 }
753 });
754
755 // Assert no constraints
756 return;
757 }
758
759 while ((! ctx.F.isZero(v.v[0].v))&&(!ctx.returnValue)) {
760 exec(ctx, ast.body);
761 if (ctx.error) return;
762
763 if (ast.step) {
764 exec(ctx, ast.step);
765 if (ctx.error) return;
766 }
767
768 v = exec(ctx, ast.condition);
769 if (ctx.error) return;
770 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition in loop cannot be an array");
771 if (v.v[0].t != "N") return ctx.throwError(ast.condition, "Condition result not a number");
772 }
773}
774
775function execCompute(ctx, ast) {
776 iterateAST(ast, (ast) => {
777 if (ast.type == "OP") {
778 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
779 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
780 }
781 }
782 });
783}
784
785function execIf(ctx, ast) {
786 let v = exec(ctx, ast.condition);
787 if (ctx.error) return;
788 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition cannot be an array");
789 if (v.v[0].t != "N") {
790 iterateAST(ast, (ast) => {
791 if (ast.type == "OP") {
792 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
793 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
794 }
795 }
796 });
797
798 // Assert no constraints
799 return;
800 }
801
802 if (!ctx.F.isZero(v.v[0].v)) {
803 exec(ctx, ast.then);
804 } else {
805 if (ast.else) {
806 exec(ctx, ast.else);
807 }
808 }
809}
810
811
812function execTerCon(ctx, ast) {
813 let v = exec(ctx, ast.values[0]);
814 if (ctx.error) return;
815 if (v.s[0] != 1) return ctx.throwError(ast.condition, "Condition cannot be an array");
816 if (v.v[0].t != "N") {
817 iterateAST(ast, (ast) => {
818 if (ast.type == "OP") {
819 if (["==>", "<==", "==="].indexOf(ast.op) >= 0) {
820 return ctx.throwError(ast.condition, "Constraint inside a calculating block");
821 }
822 }
823 });
824
825 // Assert no constraints
826 return NQVAL;
827 }
828
829 if (!ctx.F.isZero(v.v[0].v)) {
830 return exec(ctx, ast.values[1]);
831 } else {
832 return exec(ctx, ast.values[2]);
833 }
834}
835
836function execOp(ctx, ast, op, nOps) {
837 const operands = [];
838 for (let i=0; i<nOps; i++) {
839 const a = exec(ctx, ast.values[i]);
840 if (ctx.error) return;
841 const aV = val(ctx, a, ast.values[i]);
842 operands.push(aV);
843 }
844
845 return {
846 t: "V",
847 s: [1,0],
848 v: [ctx.lc[op](...operands)]
849 };
850}
851
852
853function execOpOp(ctx, ast, op, lr) {
854
855 if (ast.values[0].type != "VARIABLE") return ctx.throwError(ast, "incrementing a non variable");
856 if (!utils.isDefined(ast.values[0].refId)) return ctx.throwError(ast, "Assigning to a non variable");
857 const left = ctx.refs[ast.values[0].refId];
858 if (left.t != "V") return ctx.throwError(ast, `Only variables can use ${ast.op} operations`);
859 let leftSels = [];
860 if (ast.values[0].selectors) {
861 for (let i=0; i< ast.values[0].selectors.length; i++) {
862 const sel = exec(ctx, ast.values[0].selectors[i]);
863 if (ctx.error) return;
864
865 if (sel.s[0] != 1) return ctx.throwError(ast, "Selector cannot be an array");
866 if (sel.v[0].t != "N") return {t: "NQ"};
867
868 leftSels.push( Scalar.toNumber(sel.v[0].v) );
869 }
870 }
871 if (!left.s) return ctx.throwError(ast, "variable. not defined yet");
872
873 let o = 0;
874 let s = left.s[0];
875 for (let i=0; i<leftSels.length; i++) {
876 o += leftSels[i]*left.s[i+1];
877 s = left.s[i+1];
878 }
879 if (s != 1) return ctx.throwError(ast, `invalid operator ${ast.op} in an array`);
880 const resBefore = left.v[o];
881
882 let right;
883 if (ast.values[1]) {
884 const rightRef = exec(ctx, ast.values[1]);
885 if (ctx.error) return;
886 right = val(ctx, rightRef);
887 } else {
888 right = {t:"N", v: ctx.F.one};
889 }
890
891 if (!right) return ctx.throwError(ast, "adding a no number");
892
893 const resAfter = ctx.lc[op](resBefore, right);
894 left.v[o] = resAfter;
895
896 if (lr == "RIGHT") {
897 return {
898 t: "V",
899 s: [1,0],
900 v: [resBefore]
901 };
902 } else {
903 return {
904 t: "V",
905 s: [1,0],
906 v: [resAfter]
907 };
908 }
909}
910
911// Extract the value of a reference.
912// Must be a single value. (cannot be an array)
913// The result is an LCAlgebra compatible object {t: ["N"|"LC"|"QEX"|"NQ"], [coefs|v|a,b,c]}
914function val(ctx, a, ast) {
915 if (a.s[0] != 1) return ctx.throwError(ast, "Array not allowed");
916 if (a.t=="V") {
917 return a.v[0];
918 } else if (a.t=="S") {
919 const res = {
920 t: "LC",
921 coefs: {}
922 };
923 let sIdx = a.sIdx;
924 while (ctx.signals[sIdx].e >= 0) sIdx = ctx.signals[sIdx].e;
925 res.coefs[sIdx] = ctx.F.one;
926 return res;
927 } else {
928 ctx.throwError(ast, "Invalid type: " + a.t);
929 }
930}
931
932function execConstrain(ctx, ast) {
933 ast.fileName = ctx.fileName;
934 ast.filePath = ctx.filePath;
935 const a = exec(ctx, ast.values[0]);
936 if (ctx.error) return;
937 const aV = val(ctx, a, ast.values[0]);
938 const b = exec(ctx, ast.values[1]);
939 if (ctx.error) return;
940 const bV = val(ctx, b, ast.values[1]);
941
942 const res = ctx.lc.sub(aV,bV);
943 if (res.type == "NQ") return ctx.throwError(ast, "Non Quadratic constraint");
944
945 if (!ctx.lc.isZero(res)) {
946 ctx.constraints.push(ctx.lc.toQEX(res));
947 if (ctx.verbose) {
948 if ((ctx.constraints.length % 10000 == 0)&&(ctx.constraints.length>0)) console.log("Constraints: " + ctx.constraints.length);
949 }
950 }
951
952 return a;
953}
954
955function execSignalAssignConstrain(ctx, ast) {
956 const v = execAssignement(ctx,ast);
957 if (ctx.error) return;
958 execConstrain(ctx, ast);
959 if (ctx.error) return;
960 return v;
961}
962
963function execInclude(ctx, ast) {
964 if (ast.block) {
965
966 const oldFilePath = ctx.filePath;
967 const oldFileName = ctx.fileName;
968
969 ctx.filePath = ast.filePath;
970 ctx.fileName = ast.fileName;
971
972 exec(ctx, ast.block); // Process only one.
973
974 ctx.filePath = oldFilePath;
975 ctx.fileName = oldFileName;
976 }
977}
978
979function execArray(ctx, ast) {
980 const res = {t: "V", v:[]};
981
982
983 let subSize = null;
984
985 for (let i=0; i<ast.values.length; i++) {
986 const e = exec(ctx, ast.values[i]);
987 if (i==0) {
988 subSize=e.s;
989 } else {
990 if (!utils.sameSizes(subSize, e.s)) return ctx.throwError(ast, "Array mus be homogeneous");
991 }
992 if (e.t == "V") {
993 for (let j=0; j<e.v.length;j++) res.v.push(e.v[j]);
994 } else if (e.t == "S") {
995 for (let j=0; j<e.v.length;j++) {
996 const sv = {t: "LC", coefs: {}};
997 sv.coefs[e.sIdx+j] = ctx.F.one;
998 res.v.push(sv);
999 }
1000 } else {
1001 return ctx.throwError(ast, "Type not allowed in array");
1002 }
1003 }
1004
1005 res.s = [ast.values.length*subSize[0], ...subSize];
1006
1007 return res;
1008}
1009
1010function createRefs(ctx, ast, scope) {
1011 const scopeLabels = [];
1012 const scopes = scope ? [scope] : [{}];
1013 iterateAST(ast, (ast, level) => {
1014 while ((scopeLabels.length>0)&&(!level.startsWith(scopeLabels[scopeLabels.length-1]))) {
1015 scopes.pop();
1016 scopeLabels.pop();
1017 }
1018 if (ast.type == "DECLARE") {
1019 if (ast.declareType == "COMPONENT") {
1020 ast.refId = define(ast.name.name, {t: "C"});
1021 } else if ((ast.declareType == "SIGNALIN")||
1022 (ast.declareType == "SIGNALOUT")||
1023 (ast.declareType == "SIGNAL")) {
1024 ast.refId = define(ast.name.name, {t: "S"});
1025 } else if (ast.declareType == "VARIABLE") {
1026 ast.refId = define(ast.name.name, {t: "V", s: null});
1027 } else {
1028 return ctx.throwError(ast, "Invalid declaration: " + ast.declareType);
1029 }
1030 } else if (["BLOCK", "FOR", "IF", "WHILE", "COMPUTE"].indexOf(ast.type) >= 0) {
1031 scopeLabels.push(level);
1032 scopes.push({});
1033 } else if (ast.type == "TEMPLATEDEF") {
1034 ast.refId = define(ast.name, {t: "T"});
1035 } else if (ast.type == "FUNCTIONDEF") {
1036 ast.refId = define(ast.name, {t: "F"});
1037 } else if (ast.type == "INCLUDE") {
1038 const incFileName = path.resolve(ctx.filePath, ast.file);
1039 const incFilePath = path.dirname(incFileName);
1040
1041 ctx.includedFiles = ctx.includedFiles || [];
1042 if (ctx.includedFiles[incFileName]) {
1043 ctx.bloc = null; // Include already included...
1044 return;
1045 }
1046
1047 const src = fs.readFileSync(incFileName, "utf8");
1048
1049 if (!src) return ctx.throwError(ast, "Include file not found: "+incFileName);
1050
1051 ctx.includedFiles[incFileName] = src.split("\n");
1052
1053 ast.block = parser.parse(src);
1054 ast.filePath = incFilePath;
1055 ast.fileName = incFileName;
1056
1057 const oldFilePath = ctx.filePath;
1058 const oldFileName = ctx.fileName;
1059 ctx.filePath = incFilePath;
1060 ctx.fileName = incFileName;
1061
1062 scopes.push({});
1063 createRefs(ctx, ast.block);
1064 scopes.pop();
1065
1066 ctx.filePath = oldFilePath;
1067 ctx.fileName = oldFileName;
1068 } else if (ast.type == "VARIABLE") {
1069 ast.refId = name2ref(ast.name);
1070 }
1071
1072 function define(name, entry) {
1073 if (scopes[scopes.length-1][name]) return ctx.throwError(ast, `Name already defined: ${name}`);
1074 for (let i=scopes.length-2; i>=0; i--) {
1075 if (scopes[i][name]) ctx.logWarning(ast, `Shadowing variable: ${name}`);
1076 }
1077 const refId = ctx.refs.length;
1078 entry.refId = refId;
1079 ctx.refs.push(entry);
1080 scopes[scopes.length-1][name] = refId;
1081 return refId;
1082 }
1083
1084 function name2ref(n) {
1085 for (let i=scopes.length-1; i>=0; i--) {
1086 if (typeof scopes[i][n] !== "undefined") return scopes[i][n];
1087 }
1088 return -1;
1089 }
1090
1091
1092 });
1093
1094}
1095
1096